84 lines
2.1 KiB
PHP
84 lines
2.1 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\EntryType;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
|
|
/**
|
|
* transaction_entries — DOUBLE-ENTRY LEDGER
|
|
*
|
|
* Every transaction MUST have exactly 2 entries (debit + credit).
|
|
* balance_after_minor is the wallet balance snapshot after this entry.
|
|
* This table is the source of truth for all balance calculations.
|
|
*
|
|
* @property int $transaction_id
|
|
* @property int $wallet_id
|
|
* @property string $entry_type debit/credit
|
|
* @property int $amount_minor BIGINT
|
|
* @property int $balance_after_minor BIGINT — snapshot after this entry
|
|
*/
|
|
class TransactionEntry extends BaseModel
|
|
{
|
|
use HasFactory;
|
|
|
|
protected $fillable = [
|
|
'transaction_id',
|
|
'wallet_id',
|
|
'entry_type',
|
|
'amount_minor',
|
|
'balance_after_minor',
|
|
];
|
|
|
|
protected $casts = [
|
|
'entry_type' => EntryType::class,
|
|
'amount_minor' => 'integer',
|
|
'balance_after_minor' => 'integer',
|
|
];
|
|
|
|
public $timestamps = false; // created_at only, set via useCurrent()
|
|
|
|
// ── Relationships ──
|
|
|
|
public function transaction()
|
|
{
|
|
return $this->belongsTo(Transaction::class);
|
|
}
|
|
|
|
public function wallet()
|
|
{
|
|
return $this->belongsTo(Wallet::class);
|
|
}
|
|
|
|
// ── Scopes ──
|
|
|
|
public function scopeDebits($query)
|
|
{
|
|
return $query->where('entry_type', EntryType::DEBIT);
|
|
}
|
|
|
|
public function scopeCredits($query)
|
|
{
|
|
return $query->where('entry_type', EntryType::CREDIT);
|
|
}
|
|
|
|
public function scopeForWallet($query, int $walletId)
|
|
{
|
|
return $query->where('wallet_id', $walletId);
|
|
}
|
|
|
|
// ── Reconciliation helper ──
|
|
|
|
/**
|
|
* Verify double-entry integrity: SUM(debits) must equal SUM(credits)
|
|
* for the given transaction_id.
|
|
*/
|
|
public static function verifyIntegrity(int $transactionId): bool
|
|
{
|
|
$debits = self::where('transaction_id', $transactionId)->debits()->sum('amount_minor');
|
|
$credits = self::where('transaction_id', $transactionId)->credits()->sum('amount_minor');
|
|
|
|
return $debits === $credits && $debits > 0;
|
|
}
|
|
}
|