feat(expense): 승인완료 메뉴 — 전 직원 월별 + 엑셀/첨부 zip
- /expense/approved: 승인자/관리자 전용, 월 선택 + 전 직원 승인완료 항목 - 직원별 승인 금액 합계표 + 총합 - 엑셀 다운(리스트 + 직원별합계 시트) - 첨부 일괄 zip: 파일명 yy년 mm월 dd일(요일)_이름.확장자 (업로드일 기준) - store: APPROVED_STATUSES(승인,정산완료) / db: list_approved, approved_attachments Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="erp-ex-approved">
|
||||
|
||||
<!-- ── 상단 액션 + 월 선택 ── -->
|
||||
<div class="erp-page-actions">
|
||||
<a class="erp-btn erp-btn-ghost" href="/expense/">← 개인경비</a>
|
||||
<form id="mon-form" method="get" action="/expense/approved" style="display:flex; gap:var(--sp-8); align-items:center; margin:0;">
|
||||
<input type="month" name="month" value="{{ month }}" />
|
||||
<button type="submit" class="erp-btn erp-btn-outline">조회</button>
|
||||
</form>
|
||||
<a class="erp-btn erp-btn-primary" href="/expense/api/approved/export.xlsx?month={{ month }}">엑셀 다운</a>
|
||||
<a class="erp-btn erp-btn-outline" href="/expense/api/approved/attachments.zip?month={{ month }}">첨부 일괄(zip)</a>
|
||||
</div>
|
||||
|
||||
<!-- ── 직원별 합계 ── -->
|
||||
<div class="erp-card-block">
|
||||
<div class="erp-card-block-head">
|
||||
<h2>직원별 승인 금액 합계</h2>
|
||||
<span class="erp-muted">{{ month }} · 총 {{ "{:,}".format(grand_total) }} 원</span>
|
||||
</div>
|
||||
<div class="erp-table-wrap">
|
||||
<table class="erp-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>이름</th>
|
||||
<th>이메일</th>
|
||||
<th style="width:100px; text-align:right;">건수</th>
|
||||
<th style="width:160px; text-align:right;">합계금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in owner_summary %}
|
||||
<tr>
|
||||
<td>{{ r.name }}</td>
|
||||
<td class="erp-muted">{{ r.owner }}</td>
|
||||
<td style="text-align:right;">{{ r.count }}</td>
|
||||
<td style="text-align:right; font-variant-numeric: tabular-nums;">{{ "{:,}".format(r.total) }} 원</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="erp-empty">해당 월 승인완료 항목이 없습니다.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% if owner_summary %}
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="2" style="text-align:right;">합계</th>
|
||||
<th style="text-align:right;">{{ count }}</th>
|
||||
<th style="text-align:right;">{{ "{:,}".format(grand_total) }} 원</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 승인완료 항목 목록 ── -->
|
||||
<div class="erp-card-block">
|
||||
<div class="erp-card-block-head">
|
||||
<h2>승인완료 항목 ({{ count }}건)</h2>
|
||||
<span class="erp-muted">전 직원 · 사용일 기준 {{ month }}</span>
|
||||
</div>
|
||||
<div class="erp-table-wrap">
|
||||
<table class="erp-table" id="appr-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:110px;">사용일</th>
|
||||
<th style="width:120px;">이름</th>
|
||||
<th style="width:90px;">분류</th>
|
||||
<th style="width:100px;">수단</th>
|
||||
<th>가맹점/메모</th>
|
||||
<th style="width:120px; text-align:right;">금액</th>
|
||||
<th style="width:90px;">상태</th>
|
||||
<th style="width:60px; text-align:center;">첨부</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in items %}
|
||||
<tr data-id="{{ it.id }}">
|
||||
<td>{{ it.spent_at }}</td>
|
||||
<td>{{ it.owner_name }}<div class="erp-row-sub">{{ it.owner }}</div></td>
|
||||
<td>{{ it.category }}</td>
|
||||
<td>{{ it.method }}</td>
|
||||
<td>
|
||||
<div>{{ it.merchant }}</div>
|
||||
{% if it.memo %}<div class="erp-row-sub">{{ it.memo }}</div>{% endif %}
|
||||
</td>
|
||||
<td style="text-align:right; font-variant-numeric: tabular-nums;">{{ "{:,}".format(it.amount) }} 원</td>
|
||||
<td>
|
||||
{% if it.status == '정산완료' %}
|
||||
<span class="erp-badge erp-badge-neutral">{{ it.status }}</span>
|
||||
{% else %}
|
||||
<span class="erp-badge erp-badge-inverse">{{ it.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align:center;">
|
||||
<button class="erp-btn erp-btn-ghost erp-btn-sm js-view-att" title="첨부 보기">📎</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="8" class="erp-empty">해당 월 승인완료 항목이 없습니다.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.erp-page-actions { display:flex; gap:var(--sp-8); margin-bottom:var(--sp-16); flex-wrap:wrap; align-items:center; }
|
||||
.erp-ex-approved .erp-row-sub { font-size:12px; color:var(--color-text-muted, #888); }
|
||||
#appr-table td { vertical-align: middle; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
(function () {
|
||||
// 월 선택 변경 시 자동 조회
|
||||
const monInput = document.querySelector('#mon-form input[name="month"]');
|
||||
if (monInput) monInput.addEventListener("change", () => monInput.form.submit());
|
||||
|
||||
// 첨부 보기 (공용 뷰어)
|
||||
const tbody = document.querySelector("#appr-table tbody");
|
||||
if (tbody) {
|
||||
tbody.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("button.js-view-att");
|
||||
if (!btn) return;
|
||||
const tr = btn.closest("tr[data-id]");
|
||||
const id = tr.dataset.id;
|
||||
const who = tr.children[1].textContent.trim();
|
||||
window.ErpAttachViewer.openFor(id, { title: `첨부 — ${who}` });
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -10,6 +10,7 @@
|
||||
<a class="erp-btn erp-btn-outline" href="/expense/pending">
|
||||
승인 대기 {% if pending_count %}<strong style="margin-left:6px;">{{ pending_count }}</strong>{% endif %}
|
||||
</a>
|
||||
<a class="erp-btn erp-btn-outline" href="/expense/approved">승인완료</a>
|
||||
{% endif %}
|
||||
<a class="erp-btn erp-btn-outline" href="/expense/api/export.xlsx?scope=mine">엑셀(내 항목)</a>
|
||||
{% if is_approver %}
|
||||
|
||||
Reference in New Issue
Block a user