325 lines
14 KiB
PHP
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'; ?>
|