webmoney/backend/app/Models/LiabilityAccount.php
marco 54cccdd095 refactor: migração para desenvolvimento direto no servidor
- 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
2025-12-19 11:45:32 +01:00

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);
}
}