Files
2026-06-07 00:33:58 +09:00

325 lines
14 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/installment_service.php';
check_auth();
$pdo = db();
$uid = user_id();
$msg = '';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$mode = $_POST['mode'] ?? '';
if ($mode === 'rebuild_installments') {
$count = rebuild_all_installments_for_user($uid);
$msg = '할부 자동반영 재적용 완료 (' . number_format($count) . '건)';
}
} catch (Throwable $e) {
$error = $e->getMessage();
}
}
$ym = $_GET['ym'] ?? '';
$accountId = (int)($_GET['account_id'] ?? 0);
$q = trim($_GET['q'] ?? '');
$params = [$uid];
$where = ["i.user_id = ?"];
if ($ym !== '') {
$where[] = "i.start_year_month = ?";
$params[] = $ym;
}
if ($accountId > 0) {
$where[] = "i.account_id = ?";
$params[] = $accountId;
}
if ($q !== '') {
$where[] = "(t.merchant_name LIKE ? OR t.description LIKE ? OR a.account_name LIKE ?)";
$like = '%' . $q . '%';
$params[] = $like;
$params[] = $like;
$params[] = $like;
}
$sql = "
SELECT
i.*,
a.account_name,
a.institution_name,
t.transaction_date,
t.merchant_name,
t.description,
COALESCE(SUM(CASE WHEN s.is_billed = 0 THEN s.principal_amount ELSE 0 END), 0) AS remaining_principal,
COALESCE(SUM(CASE WHEN s.is_billed = 0 THEN s.interest_amount ELSE 0 END), 0) AS remaining_interest,
COALESCE(SUM(CASE WHEN s.is_billed = 0 THEN s.total_amount ELSE 0 END), 0) AS remaining_total,
COALESCE(SUM(CASE WHEN s.is_billed = 1 THEN s.total_amount ELSE 0 END), 0) AS billed_total,
COALESCE(MIN(CASE WHEN s.is_billed = 0 THEN s.cycle_no ELSE NULL END), 0) AS next_cycle
FROM installments i
JOIN accounts a ON a.id = i.account_id
JOIN transactions t ON t.id = i.transaction_id
LEFT JOIN installment_schedules s ON s.installment_id = i.id
WHERE " . implode(' AND ', $where) . "
GROUP BY
i.id, i.user_id, i.transaction_id, i.account_id,
i.principal_amount, i.interest_total, i.total_billed_amount,
i.installment_months, i.annual_interest_rate, i.start_year_month,
i.interest_type, i.current_cycle, i.is_completed,
i.prepaid_principal_amount, i.prepaid_interest_amount,
i.created_at, i.updated_at,
a.account_name, a.institution_name,
t.transaction_date, t.merchant_name, t.description
ORDER BY i.created_at DESC, i.id DESC
";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$installments = $stmt->fetchAll();
$stmt = $pdo->prepare("
SELECT id, account_name, institution_name
FROM accounts
WHERE user_id = ?
AND is_active = 1
AND account_type = 'card'
ORDER BY id ASC
");
$stmt->execute([$uid]);
$cardAccounts = $stmt->fetchAll();
require __DIR__ . '/../app/views/header.php';
?>
<div class="page-head">
<h2>할부 내역</h2>
<div class="d-flex flex-wrap gap-2">
<a href="/installment_billing.php" class="btn btn-outline-primary">
할부 청구 관리
</a>
<form method="post" class="d-inline">
<input type="hidden" name="mode" value="rebuild_installments">
<button
class="btn btn-outline-danger"
onclick="return confirm('기존 할부 스케줄을 초기화 후 재생성합니다. 진행하시겠습니까?');">
자동반영 리셋 후 재적용
</button>
</form>
</div>
</div>
<?php if ($msg): ?>
<div class="alert alert-success"><?= h($msg) ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger"><?= h($error) ?></div>
<?php endif; ?>
<div class="card finance-card mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-12 col-md-3">
<label class="form-label">시작월</label>
<input type="month" name="ym" class="form-control" value="<?= h($ym) ?>">
</div>
<div class="col-12 col-md-3">
<label class="form-label">카드</label>
<select name="account_id" class="form-select">
<option value="0">전체</option>
<?php foreach ($cardAccounts as $acc): ?>
<option value="<?= $acc['id'] ?>" <?= $accountId === (int)$acc['id'] ? 'selected' : '' ?>>
<?= h($acc['institution_name']) ?> / <?= h($acc['account_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-4">
<label class="form-label">검색</label>
<input type="text" name="q" class="form-control" value="<?= h($q) ?>" placeholder="사용처, 메모, 카드명">
</div>
<div class="col-12 col-md-2 d-flex align-items-end gap-2">
<button class="btn btn-primary w-100">조회</button>
<a href="/installments.php" class="btn btn-outline-secondary w-100">초기화</a>
</div>
</form>
</div>
</div>
<div class="row g-3">
<?php foreach ($installments as $row): ?>
<?php
$progress = 0;
if ((float)$row['total_billed_amount'] > 0) {
$progress = (($row['total_billed_amount'] - $row['remaining_total']) / (float)$row['total_billed_amount']) * 100;
$progress = max(0, min(100, $progress));
}
$scheduleStmt = $pdo->prepare("
SELECT *
FROM installment_schedules
WHERE installment_id = ?
ORDER BY cycle_no ASC
");
$scheduleStmt->execute([$row['id']]);
$schedules = $scheduleStmt->fetchAll();
?>
<div class="col-12">
<div class="card finance-card">
<div class="card-body">
<div class="d-flex flex-column flex-xl-row justify-content-between gap-3">
<div>
<div class="eyebrow"><?= h($row['institution_name']) ?> / <?= h($row['account_name']) ?></div>
<div class="card-title-lg"><?= h($row['merchant_name'] ?: '사용처 없음') ?></div>
<div class="text-secondary small">
결제일 <?= h($row['transaction_date']) ?>
<?php if (!empty($row['description'])): ?>
· <?= h($row['description']) ?>
<?php endif; ?>
</div>
</div>
<div class="text-xl-end">
<span class="badge <?= (float)$row['remaining_total'] > 0 ? 'text-bg-warning' : 'text-bg-success' ?>">
<?= (float)$row['remaining_total'] > 0 ? '진행중' : '완료' ?>
</span>
<div class="small text-secondary mt-2">연이자율 <?= h((string)$row['annual_interest_rate']) ?>%</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($row['principal_amount']) ?></div>
</div>
<div class="col-6 col-md-3">
<div class="stat-label">총 할부이자</div>
<div class="stat-value text-danger"><?= won($row['interest_total']) ?></div>
</div>
<div class="col-6 col-md-3">
<div class="stat-label">총 청구금액</div>
<div class="stat-value"><?= won($row['total_billed_amount']) ?></div>
</div>
<div class="col-6 col-md-3">
<div class="stat-label">개월 수</div>
<div class="stat-value"><?= h((string)$row['installment_months']) ?>개월</div>
</div>
<div class="col-6 col-md-3">
<div class="stat-label">남은 원금</div>
<div class="stat-value"><?= won($row['remaining_principal']) ?></div>
</div>
<div class="col-6 col-md-3">
<div class="stat-label">남은 이자</div>
<div class="stat-value text-danger"><?= won($row['remaining_interest']) ?></div>
</div>
<div class="col-6 col-md-3">
<div class="stat-label">남은 총액</div>
<div class="stat-value"><?= won($row['remaining_total']) ?></div>
</div>
<div class="col-6 col-md-3">
<div class="stat-label">다음 회차</div>
<div class="stat-value"><?= (int)$row['next_cycle'] > 0 ? ((int)$row['next_cycle'] . '회차') : '-' ?></div>
</div>
</div>
<div class="mt-3">
<div class="progress finance-progress">
<div class="progress-bar" role="progressbar" style="width: <?= number_format($progress, 1) ?>%"></div>
</div>
<div class="small text-secondary mt-2">진행률 <?= number_format($progress, 1) ?>%</div>
</div>
<div class="mt-3 d-flex flex-wrap gap-2">
<button
class="btn btn-sm btn-outline-primary"
type="button"
data-bs-toggle="collapse"
data-bs-target="#schedule-<?= $row['id'] ?>"
>
회차별 보기
</button>
<?php if ((float)$row['remaining_total'] > 0): ?>
<a href="/installment_prepay.php?id=<?= $row['id'] ?>" class="btn btn-sm btn-outline-danger">선결제 / 중도상환</a>
<?php endif; ?>
<a href="/installment_billing.php?ym=<?= h($row['start_year_month']) ?>&account_id=<?= $row['account_id'] ?>&q=<?= urlencode((string)($row['merchant_name'] ?? '')) ?>" class="btn btn-sm btn-outline-secondary">
청구관리로 보기
</a>
</div>
<div class="collapse mt-3" id="schedule-<?= $row['id'] ?>">
<div class="mobile-scroll">
<table class="table align-middle mb-0">
<thead>
<tr>
<th>회차</th>
<th>청구월</th>
<th class="text-end">원금</th>
<th class="text-end">이자</th>
<th class="text-end">합계</th>
<th>상태</th>
</tr>
</thead>
<tbody>
<?php foreach ($schedules as $s): ?>
<tr>
<td><?= $s['cycle_no'] ?>회차</td>
<td><?= h($s['bill_year_month']) ?></td>
<td class="text-end"><?= won($s['principal_amount']) ?></td>
<td class="text-end text-danger"><?= won($s['interest_amount']) ?></td>
<td class="text-end fw-bold"><?= won($s['total_amount']) ?></td>
<td>
<?php if ((int)$s['is_billed'] === 1): ?>
<span class="badge text-bg-success">청구완료</span>
<?php else: ?>
<span class="badge text-bg-secondary">미청구</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php if (!$schedules): ?>
<tr>
<td colspan="6" class="text-center text-secondary py-4">회차 정보가 없습니다.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<?php if (!$installments): ?>
<div class="col-12">
<div class="card finance-card">
<div class="card-body text-center text-secondary py-5">
등록된 할부 내역이 없습니다.
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php require __DIR__ . '/../app/views/footer.php'; ?>