- Removido README.md padrão do Laravel (backend) - Removidos scripts de deploy (não mais necessários) - Atualizado copilot-instructions.md para novo fluxo - Adicionada documentação de auditoria do servidor - Sincronizado código de produção com repositório Novo workflow: - Trabalhamos diretamente em /root/webmoney (symlink para /var/www/webmoney) - Mudanças PHP são instantâneas - Mudanças React requerem 'npm run build' - Commit após validação funcional
424 lines
15 KiB
PHP
Executable File
424 lines
15 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
class LiabilityAccount extends Model
|
|
{
|
|
use HasFactory, SoftDeletes;
|
|
|
|
/**
|
|
* Status do contrato
|
|
*/
|
|
public const STATUS_ACTIVE = 'active';
|
|
public const STATUS_PAID_OFF = 'paid_off';
|
|
public const STATUS_DEFAULTED = 'defaulted';
|
|
public const STATUS_RENEGOTIATED = 'renegotiated';
|
|
|
|
public const STATUSES = [
|
|
self::STATUS_ACTIVE => 'Activo',
|
|
self::STATUS_PAID_OFF => 'Liquidado',
|
|
self::STATUS_DEFAULTED => 'En mora',
|
|
self::STATUS_RENEGOTIATED => 'Renegociado',
|
|
];
|
|
|
|
/**
|
|
* Tipos de contrato
|
|
*/
|
|
public const CONTRACT_TYPE_PERSONAL_LOAN = 'personal_loan';
|
|
public const CONTRACT_TYPE_VEHICLE = 'vehicle_financing';
|
|
public const CONTRACT_TYPE_MORTGAGE = 'mortgage';
|
|
public const CONTRACT_TYPE_CREDIT_CARD = 'credit_card';
|
|
public const CONTRACT_TYPE_CONSORTIUM = 'consortium';
|
|
public const CONTRACT_TYPE_LEASING = 'leasing';
|
|
public const CONTRACT_TYPE_OVERDRAFT = 'overdraft';
|
|
public const CONTRACT_TYPE_PAYROLL_LOAN = 'payroll_loan';
|
|
public const CONTRACT_TYPE_OTHER = 'other';
|
|
|
|
public const CONTRACT_TYPES = [
|
|
self::CONTRACT_TYPE_PERSONAL_LOAN => [
|
|
'name' => 'Préstamo Personal',
|
|
'description' => 'Préstamo con cuotas fijas (Sistema PRICE)',
|
|
'icon' => 'banknotes',
|
|
'default_amortization' => 'price',
|
|
'fields' => ['principal_amount', 'annual_interest_rate', 'total_installments', 'start_date', 'first_due_date'],
|
|
],
|
|
self::CONTRACT_TYPE_VEHICLE => [
|
|
'name' => 'Financiación de Vehículo',
|
|
'description' => 'Crédito para compra de coche o moto',
|
|
'icon' => 'truck',
|
|
'default_amortization' => 'price',
|
|
'fields' => ['principal_amount', 'annual_interest_rate', 'total_installments', 'start_date', 'first_due_date', 'asset_value'],
|
|
],
|
|
self::CONTRACT_TYPE_MORTGAGE => [
|
|
'name' => 'Hipoteca / Financiación Inmobiliaria',
|
|
'description' => 'Crédito para compra de inmueble',
|
|
'icon' => 'home',
|
|
'default_amortization' => 'sac',
|
|
'fields' => ['principal_amount', 'annual_interest_rate', 'total_installments', 'start_date', 'first_due_date', 'asset_value', 'insurance_amount'],
|
|
],
|
|
self::CONTRACT_TYPE_CREDIT_CARD => [
|
|
'name' => 'Tarjeta de Crédito',
|
|
'description' => 'Financiación de compras a plazos',
|
|
'icon' => 'credit-card',
|
|
'default_amortization' => 'price',
|
|
'fields' => ['principal_amount', 'annual_interest_rate', 'total_installments', 'first_due_date'],
|
|
],
|
|
self::CONTRACT_TYPE_CONSORTIUM => [
|
|
'name' => 'Consorcio',
|
|
'description' => 'Grupo de compras con cuotas variables',
|
|
'icon' => 'users',
|
|
'default_amortization' => 'consortium',
|
|
'fields' => ['principal_amount', 'total_installments', 'admin_fee_percent', 'start_date', 'first_due_date'],
|
|
],
|
|
self::CONTRACT_TYPE_LEASING => [
|
|
'name' => 'Leasing',
|
|
'description' => 'Arrendamiento con opción de compra',
|
|
'icon' => 'key',
|
|
'default_amortization' => 'price',
|
|
'fields' => ['principal_amount', 'annual_interest_rate', 'total_installments', 'start_date', 'first_due_date', 'residual_value'],
|
|
],
|
|
self::CONTRACT_TYPE_OVERDRAFT => [
|
|
'name' => 'Descubierto / Cheque Especial',
|
|
'description' => 'Línea de crédito rotativa',
|
|
'icon' => 'arrow-trending-down',
|
|
'default_amortization' => 'american',
|
|
'fields' => ['principal_amount', 'monthly_interest_rate'],
|
|
],
|
|
self::CONTRACT_TYPE_PAYROLL_LOAN => [
|
|
'name' => 'Préstamo con Nómina',
|
|
'description' => 'Crédito con descuento en nómina',
|
|
'icon' => 'briefcase',
|
|
'default_amortization' => 'price',
|
|
'fields' => ['principal_amount', 'annual_interest_rate', 'total_installments', 'start_date', 'first_due_date'],
|
|
],
|
|
self::CONTRACT_TYPE_OTHER => [
|
|
'name' => 'Otro',
|
|
'description' => 'Otro tipo de pasivo',
|
|
'icon' => 'document-text',
|
|
'default_amortization' => 'price',
|
|
'fields' => ['principal_amount', 'total_installments', 'first_due_date'],
|
|
],
|
|
];
|
|
|
|
/**
|
|
* Sistemas de amortização
|
|
*/
|
|
public const AMORTIZATION_PRICE = 'price';
|
|
public const AMORTIZATION_SAC = 'sac';
|
|
public const AMORTIZATION_AMERICAN = 'american';
|
|
public const AMORTIZATION_CONSORTIUM = 'consortium';
|
|
|
|
public const AMORTIZATION_SYSTEMS = [
|
|
self::AMORTIZATION_PRICE => [
|
|
'name' => 'PRICE (Cuota Fija)',
|
|
'description' => 'Cuotas iguales. Intereses decrecientes, amortización creciente.',
|
|
],
|
|
self::AMORTIZATION_SAC => [
|
|
'name' => 'SAC (Amortización Constante)',
|
|
'description' => 'Amortización fija. Cuotas e intereses decrecientes.',
|
|
],
|
|
self::AMORTIZATION_AMERICAN => [
|
|
'name' => 'Americano',
|
|
'description' => 'Solo intereses durante el plazo, principal al final.',
|
|
],
|
|
self::AMORTIZATION_CONSORTIUM => [
|
|
'name' => 'Consorcio',
|
|
'description' => 'Cuotas variables según el grupo.',
|
|
],
|
|
];
|
|
|
|
/**
|
|
* Tipos de indexadores
|
|
*/
|
|
public const INDEX_TYPES = [
|
|
'fixed' => ['name' => 'Tasa Fija', 'description' => 'Sin indexación'],
|
|
'cdi' => ['name' => 'CDI', 'description' => 'Certificado de Depósito Interbancario (Brasil)'],
|
|
'selic' => ['name' => 'SELIC', 'description' => 'Tasa básica de interés (Brasil)'],
|
|
'ipca' => ['name' => 'IPCA', 'description' => 'Índice de precios al consumidor (Brasil)'],
|
|
'igpm' => ['name' => 'IGP-M', 'description' => 'Índice general de precios (Brasil)'],
|
|
'tr' => ['name' => 'TR', 'description' => 'Tasa referencial (Brasil)'],
|
|
'euribor' => ['name' => 'Euribor', 'description' => 'Euro Interbank Offered Rate (UE)'],
|
|
'libor' => ['name' => 'LIBOR', 'description' => 'London Interbank Offered Rate'],
|
|
'sofr' => ['name' => 'SOFR', 'description' => 'Secured Overnight Financing Rate (EUA)'],
|
|
'prime' => ['name' => 'Prime Rate', 'description' => 'Tasa preferencial (EUA)'],
|
|
'ipc' => ['name' => 'IPC', 'description' => 'Índice de precios al consumidor (España)'],
|
|
'other' => ['name' => 'Otro', 'description' => 'Otro indexador'],
|
|
];
|
|
|
|
/**
|
|
* Tipos de garantia
|
|
*/
|
|
public const GUARANTEE_TYPES = [
|
|
'none' => ['name' => 'Sin garantía', 'description' => 'Préstamo sin garantía'],
|
|
'fiduciary_alienation' => ['name' => 'Alienación Fiduciaria', 'description' => 'El bien queda en garantía hasta el pago total'],
|
|
'mortgage' => ['name' => 'Hipoteca', 'description' => 'Garantía sobre inmueble'],
|
|
'pledge' => ['name' => 'Prenda', 'description' => 'Garantía sobre bien mueble'],
|
|
'guarantor' => ['name' => 'Fiador/Avalista', 'description' => 'Persona que garantiza la deuda'],
|
|
'payroll' => ['name' => 'Descuento en Nómina', 'description' => 'Descuento directo del salario'],
|
|
'investment' => ['name' => 'Inversión', 'description' => 'Garantía con inversiones/aplicaciones'],
|
|
'letter_of_credit' => ['name' => 'Carta de Crédito', 'description' => 'Garantía bancaria'],
|
|
'surety_bond' => ['name' => 'Seguro Fianza', 'description' => 'Seguro que garantiza la obligación'],
|
|
'other' => ['name' => 'Otra', 'description' => 'Otro tipo de garantía'],
|
|
];
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'account_id',
|
|
'name',
|
|
'contract_type',
|
|
'amortization_system',
|
|
'contract_number',
|
|
'creditor',
|
|
'description',
|
|
'principal_amount',
|
|
'total_interest',
|
|
'total_fees',
|
|
'total_contract_value',
|
|
'total_paid',
|
|
'total_pending',
|
|
'principal_paid',
|
|
'interest_paid',
|
|
'fees_paid',
|
|
'monthly_interest_rate',
|
|
'annual_interest_rate',
|
|
'total_interest_rate',
|
|
'total_installments',
|
|
'paid_installments',
|
|
'pending_installments',
|
|
'start_date',
|
|
'end_date',
|
|
'first_due_date',
|
|
'has_grace_period',
|
|
'grace_period_months',
|
|
'currency',
|
|
'color',
|
|
'icon',
|
|
'status',
|
|
'is_active',
|
|
// Campos avançados - Indexadores
|
|
'index_type',
|
|
'index_spread',
|
|
'total_effective_cost',
|
|
// Campos avançados - Garantias
|
|
'guarantee_type',
|
|
'guarantee_value',
|
|
'guarantee_description',
|
|
'guarantor_name',
|
|
// Campos avançados - Penalidades
|
|
'late_fee_percent',
|
|
'daily_penalty_percent',
|
|
'grace_days_for_penalty',
|
|
// Campos avançados - Específicos por tipo
|
|
'asset_value',
|
|
'asset_description',
|
|
'residual_value',
|
|
'admin_fee_percent',
|
|
'reserve_fund_percent',
|
|
// Campos avançados - Covenants e gestão
|
|
'covenants',
|
|
'alert_days_before',
|
|
'internal_responsible',
|
|
'internal_notes',
|
|
'document_number',
|
|
'registry_office',
|
|
];
|
|
|
|
protected $casts = [
|
|
'principal_amount' => 'decimal:2',
|
|
'total_interest' => 'decimal:2',
|
|
'total_fees' => 'decimal:2',
|
|
'total_contract_value' => 'decimal:2',
|
|
'total_paid' => 'decimal:2',
|
|
'total_pending' => 'decimal:2',
|
|
'principal_paid' => 'decimal:2',
|
|
'interest_paid' => 'decimal:2',
|
|
'fees_paid' => 'decimal:2',
|
|
'monthly_interest_rate' => 'decimal:4',
|
|
'annual_interest_rate' => 'decimal:4',
|
|
'total_interest_rate' => 'decimal:4',
|
|
'start_date' => 'date',
|
|
'end_date' => 'date',
|
|
'first_due_date' => 'date',
|
|
'is_active' => 'boolean',
|
|
'has_grace_period' => 'boolean',
|
|
'grace_period_months' => 'integer',
|
|
// Campos avançados
|
|
'index_spread' => 'decimal:4',
|
|
'total_effective_cost' => 'decimal:4',
|
|
'guarantee_value' => 'decimal:2',
|
|
'late_fee_percent' => 'decimal:2',
|
|
'daily_penalty_percent' => 'decimal:4',
|
|
'grace_days_for_penalty' => 'integer',
|
|
'asset_value' => 'decimal:2',
|
|
'residual_value' => 'decimal:2',
|
|
'admin_fee_percent' => 'decimal:2',
|
|
'reserve_fund_percent' => 'decimal:2',
|
|
'covenants' => 'array',
|
|
'alert_days_before' => 'integer',
|
|
];
|
|
|
|
protected $appends = ['progress_percentage', 'remaining_balance'];
|
|
|
|
/**
|
|
* Relação com o usuário
|
|
*/
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
/**
|
|
* Relação com a conta geral (opcional)
|
|
*/
|
|
public function account(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Account::class);
|
|
}
|
|
|
|
/**
|
|
* Parcelas do contrato
|
|
*/
|
|
public function installments(): HasMany
|
|
{
|
|
return $this->hasMany(LiabilityInstallment::class)->orderBy('installment_number');
|
|
}
|
|
|
|
/**
|
|
* Parcelas pagas
|
|
*/
|
|
public function paidInstallments(): HasMany
|
|
{
|
|
return $this->hasMany(LiabilityInstallment::class)->where('status', 'paid');
|
|
}
|
|
|
|
/**
|
|
* Parcelas pendentes
|
|
*/
|
|
public function pendingInstallments(): HasMany
|
|
{
|
|
return $this->hasMany(LiabilityInstallment::class)->where('status', 'pending');
|
|
}
|
|
|
|
/**
|
|
* Próxima parcela a vencer
|
|
*/
|
|
public function nextInstallment()
|
|
{
|
|
return $this->installments()
|
|
->where('status', 'pending')
|
|
->orderBy('due_date')
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* Percentual de progresso (quanto já foi pago do principal)
|
|
*/
|
|
public function getProgressPercentageAttribute(): float
|
|
{
|
|
if ($this->principal_amount <= 0) {
|
|
return 0;
|
|
}
|
|
return round(($this->principal_paid / $this->principal_amount) * 100, 2);
|
|
}
|
|
|
|
/**
|
|
* Saldo restante do principal
|
|
*/
|
|
public function getRemainingBalanceAttribute(): float
|
|
{
|
|
return $this->principal_amount - $this->principal_paid;
|
|
}
|
|
|
|
/**
|
|
* Recalcular totais baseado nas parcelas
|
|
*/
|
|
public function recalculateTotals(): void
|
|
{
|
|
$installments = $this->installments()->get();
|
|
|
|
$this->total_installments = $installments->count();
|
|
$this->paid_installments = $installments->where('status', 'paid')->count();
|
|
$this->pending_installments = $installments->where('status', 'pending')->count();
|
|
|
|
// Totais do contrato
|
|
$this->total_interest = $installments->sum('interest_amount');
|
|
$this->total_fees = $installments->sum('fee_amount');
|
|
$this->principal_amount = $installments->sum('principal_amount');
|
|
$this->total_contract_value = $installments->sum('installment_amount');
|
|
|
|
// Valores pagos
|
|
$paidInstallments = $installments->where('status', 'paid');
|
|
$this->total_paid = $paidInstallments->sum('installment_amount');
|
|
$this->principal_paid = $paidInstallments->sum('principal_amount');
|
|
$this->interest_paid = $paidInstallments->sum('interest_amount');
|
|
$this->fees_paid = $paidInstallments->sum('fee_amount');
|
|
|
|
// Valores pendentes
|
|
$pendingInstallments = $installments->where('status', 'pending');
|
|
$this->total_pending = $pendingInstallments->sum('installment_amount');
|
|
|
|
// Calcular taxas de juros
|
|
$this->calculateInterestRates();
|
|
|
|
// Datas
|
|
$firstInstallment = $installments->sortBy('due_date')->first();
|
|
$lastInstallment = $installments->sortBy('due_date')->last();
|
|
|
|
if ($firstInstallment) {
|
|
$this->first_due_date = $firstInstallment->due_date;
|
|
$this->start_date = $firstInstallment->due_date;
|
|
}
|
|
if ($lastInstallment) {
|
|
$this->end_date = $lastInstallment->due_date;
|
|
}
|
|
|
|
// Atualizar status
|
|
if ($this->pending_installments === 0 && $this->paid_installments > 0) {
|
|
$this->status = self::STATUS_PAID_OFF;
|
|
}
|
|
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Calcular taxas de juros baseado nos dados
|
|
*/
|
|
protected function calculateInterestRates(): void
|
|
{
|
|
if ($this->principal_amount <= 0) {
|
|
return;
|
|
}
|
|
|
|
// Taxa total do contrato
|
|
$this->total_interest_rate = round(($this->total_interest / $this->principal_amount) * 100, 4);
|
|
|
|
// Taxa mensal média
|
|
if ($this->total_installments > 0) {
|
|
$this->monthly_interest_rate = round($this->total_interest_rate / $this->total_installments, 4);
|
|
$this->annual_interest_rate = round($this->monthly_interest_rate * 12, 4);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scope para contas ativas
|
|
*/
|
|
public function scopeActive($query)
|
|
{
|
|
return $query->where('is_active', true);
|
|
}
|
|
|
|
/**
|
|
* Scope para um status específico
|
|
*/
|
|
public function scopeOfStatus($query, string $status)
|
|
{
|
|
return $query->where('status', $status);
|
|
}
|
|
}
|