276 lines
11 KiB
PHP
276 lines
11 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../app/lib/auth.php';
|
|
require_once __DIR__ . '/../app/lib/db.php';
|
|
require_once __DIR__ . '/../app/lib/helpers.php';
|
|
require_once __DIR__ . '/../app/lib/loan_service.php';
|
|
|
|
check_auth();
|
|
|
|
$pdo = db();
|
|
$uid = user_id();
|
|
$error = '';
|
|
$msg = '';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
try {
|
|
$mode = $_POST['mode'] ?? '';
|
|
|
|
if ($mode === 'delete') {
|
|
$loanId = (int)($_POST['loan_id'] ?? 0);
|
|
|
|
if ($loanId <= 0) {
|
|
throw new RuntimeException('대출 ID가 올바르지 않습니다.');
|
|
}
|
|
|
|
delete_loan_with_history($uid, $loanId);
|
|
$msg = '대출 및 관련 자동 생성 이력이 삭제되었습니다.';
|
|
}
|
|
|
|
if ($mode === 'reset_auto_history') {
|
|
$loanId = (int)($_POST['loan_id'] ?? 0);
|
|
|
|
if ($loanId <= 0) {
|
|
throw new RuntimeException('대출 ID가 올바르지 않습니다.');
|
|
}
|
|
|
|
reset_loan_auto_history_and_reapply($uid, $loanId, date('Y-m-d'));
|
|
$msg = '자동반영 이력을 초기화하고 오늘 기준으로 다시 반영했습니다.';
|
|
}
|
|
} catch (Throwable $e) {
|
|
$error = $e->getMessage();
|
|
}
|
|
}
|
|
|
|
$stmt = $pdo->prepare("
|
|
SELECT
|
|
l.*,
|
|
a.account_name,
|
|
a.institution_name,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM loan_schedules ls
|
|
WHERE ls.loan_id = l.id
|
|
AND ls.is_paid = 0
|
|
) AS unpaid_count,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM loan_schedules ls
|
|
WHERE ls.loan_id = l.id
|
|
AND ls.is_paid = 1
|
|
) AS paid_count,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM loan_schedules ls
|
|
WHERE ls.loan_id = l.id
|
|
) AS total_count,
|
|
(
|
|
SELECT MIN(ls.due_date)
|
|
FROM loan_schedules ls
|
|
WHERE ls.loan_id = l.id
|
|
AND ls.is_paid = 0
|
|
) AS next_due_date,
|
|
(
|
|
SELECT MAX(ls.due_date)
|
|
FROM loan_schedules ls
|
|
WHERE ls.loan_id = l.id
|
|
) AS maturity_due_date,
|
|
(
|
|
SELECT ls.scheduled_total
|
|
FROM loan_schedules ls
|
|
WHERE ls.loan_id = l.id
|
|
AND ls.is_paid = 0
|
|
ORDER BY ls.due_date ASC, ls.cycle_no ASC
|
|
LIMIT 1
|
|
) AS next_payment_amount,
|
|
(
|
|
SELECT ls.payment_phase
|
|
FROM loan_schedules ls
|
|
WHERE ls.loan_id = l.id
|
|
AND ls.is_paid = 0
|
|
ORDER BY ls.due_date ASC, ls.cycle_no ASC
|
|
LIMIT 1
|
|
) AS next_payment_phase,
|
|
(
|
|
SELECT COALESCE(SUM(ls.scheduled_total), 0)
|
|
FROM loan_schedules ls
|
|
WHERE ls.loan_id = l.id
|
|
AND ls.is_paid = 0
|
|
) AS remaining_total_amount
|
|
FROM loans l
|
|
LEFT JOIN accounts a ON a.id = l.account_id
|
|
WHERE l.user_id = ?
|
|
ORDER BY
|
|
CASE WHEN l.status = 'active' THEN 0 ELSE 1 END,
|
|
l.id DESC
|
|
");
|
|
$stmt->execute([$uid]);
|
|
$loans = $stmt->fetchAll();
|
|
|
|
require __DIR__ . '/../app/views/header.php';
|
|
?>
|
|
|
|
<div class="page-head">
|
|
<h2>대출 목록</h2>
|
|
<a href="/loan_create.php" class="btn btn-primary">대출 등록</a>
|
|
</div>
|
|
|
|
<?php if ($error): ?>
|
|
<div class="alert alert-danger"><?= h($error) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($msg): ?>
|
|
<div class="alert alert-success"><?= h($msg) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="row g-3">
|
|
<?php foreach ($loans as $loan): ?>
|
|
<?php
|
|
$summary = get_loan_remaining_summary((int)$loan['id']);
|
|
|
|
$paidCount = (int)($loan['paid_count'] ?? 0);
|
|
$totalCount = (int)($loan['total_count'] ?? 0);
|
|
$progress = $totalCount > 0 ? round(($paidCount / $totalCount) * 100, 1) : 0.0;
|
|
|
|
$phaseText = '-';
|
|
if (($loan['next_payment_phase'] ?? '') === 'grace') {
|
|
$phaseText = '거치 중';
|
|
} elseif (($loan['next_payment_phase'] ?? '') === 'repayment') {
|
|
$phaseText = '상환 중';
|
|
} elseif ($loan['status'] === 'closed') {
|
|
$phaseText = '종료';
|
|
}
|
|
?>
|
|
<div class="col-12 col-xxl-6">
|
|
<div class="card finance-card h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap">
|
|
<div>
|
|
<div class="eyebrow">
|
|
<?= h($loan['lender_name'] ?: '-') ?>
|
|
<?php if (!empty($loan['account_name'])): ?>
|
|
· <?= h($loan['account_name']) ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="card-title-lg"><?= h($loan['loan_name']) ?></div>
|
|
</div>
|
|
|
|
<div class="text-end">
|
|
<span class="badge <?= $loan['status'] === 'active' ? 'text-bg-warning' : 'text-bg-success' ?>">
|
|
<?= $loan['status'] === 'active' ? '진행중' : '종료' ?>
|
|
</span>
|
|
<div class="small text-secondary mt-2"><?= h($phaseText) ?></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mt-2">
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">대출원금</div>
|
|
<div class="stat-value"><?= won($loan['principal_amount']) ?></div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">연이자율</div>
|
|
<div class="stat-value"><?= h((string)$loan['annual_interest_rate']) ?>%</div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">거치기간</div>
|
|
<div class="stat-value"><?= h((string)$loan['grace_period_months']) ?>개월</div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">상환기간</div>
|
|
<div class="stat-value"><?= h((string)$loan['repayment_months']) ?>개월</div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">남은 원금</div>
|
|
<div class="stat-value"><?= won($summary['remaining_principal']) ?></div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">남은 이자</div>
|
|
<div class="stat-value text-danger"><?= won($summary['remaining_interest']) ?></div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">남은 총액</div>
|
|
<div class="stat-value"><?= won($loan['remaining_total_amount'] ?? 0) ?></div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">다음 회차 금액</div>
|
|
<div class="stat-value"><?= won($loan['next_payment_amount'] ?? 0) ?></div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">다음 납부일</div>
|
|
<div class="stat-value"><?= h($loan['next_due_date'] ?: '-') ?></div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">만기일자</div>
|
|
<div class="stat-value"><?= h($loan['maturity_due_date'] ?: '-') ?></div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">남은 회차 수</div>
|
|
<div class="stat-value"><?= h((string)$loan['unpaid_count']) ?>회</div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-3">
|
|
<div class="stat-label">진행률</div>
|
|
<div class="stat-value"><?= number_format($progress, 1) ?>%</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<div class="finance-progress">
|
|
<div class="progress-bar" role="progressbar" style="width: <?= $progress ?>%"></div>
|
|
</div>
|
|
<div class="small text-secondary mt-2">
|
|
완료 <?= h((string)$paidCount) ?>회 / 전체 <?= h((string)$totalCount) ?>회
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3 d-flex flex-wrap gap-2">
|
|
<a href="/loan_detail.php?id=<?= $loan['id'] ?>" class="btn btn-sm btn-outline-primary">상세/스케줄</a>
|
|
<a href="/loan_edit.php?id=<?= $loan['id'] ?>" class="btn btn-sm btn-outline-secondary">수정</a>
|
|
|
|
<?php if ($loan['status'] === 'active'): ?>
|
|
<a href="/loan_prepay.php?id=<?= $loan['id'] ?>" class="btn btn-sm btn-outline-danger">중도상환</a>
|
|
|
|
<form method="post" class="d-inline">
|
|
<input type="hidden" name="mode" value="reset_auto_history">
|
|
<input type="hidden" name="loan_id" value="<?= $loan['id'] ?>">
|
|
<button class="btn btn-sm btn-outline-warning" onclick="return confirm('자동반영 이력과 자동 생성 거래를 삭제하고 오늘 기준으로 다시 반영합니다. 계속하시겠습니까?');">
|
|
자동반영 리셋 후 재적용
|
|
</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
|
|
<form method="post" class="d-inline">
|
|
<input type="hidden" name="mode" value="delete">
|
|
<input type="hidden" name="loan_id" value="<?= $loan['id'] ?>">
|
|
<button class="btn btn-sm btn-outline-dark" onclick="return confirm('대출, 상환 스케줄, 대출 납부이력, 자동 생성 거래내역까지 삭제됩니다. 계속하시겠습니까?');">
|
|
삭제
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
|
|
<?php if (!$loans): ?>
|
|
<div class="col-12">
|
|
<div class="card finance-card">
|
|
<div class="card-body empty-state">
|
|
등록된 대출이 없습니다.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php require __DIR__ . '/../app/views/footer.php'; ?>
|