feat(expense): 전부 다운로드 zip에 승인 목록 엑셀 동봉

- _build_approved_xlsx 헬퍼로 엑셀 생성 로직 추출(export/zip 공용)
- zip 최상단에 승인목록_{month}.xlsx 포함 + 첨부 파일들

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 17:53:40 +09:00
parent 09e2f06a14
commit 6ae0d90b97
+23 -11
View File
@@ -402,19 +402,12 @@ async def approved_page(request: Request) -> HTMLResponse:
)
@router.get("/api/approved/export.xlsx")
async def approved_export(
request: Request,
user: dict[str, Any] = Depends(_require_approver),
) -> StreamingResponse:
def _build_approved_xlsx(
items: list[dict[str, Any]], names: dict[str, str]
) -> io.BytesIO:
"""승인완료 목록 + 직원별합계 시트 xlsx 바이트 생성."""
from openpyxl import Workbook # 지연 import
store = _store(request)
_require_db_store(store)
year, mon, raw = _parse_month(request)
items = store.list_approved(year=year, month=mon)
names = _name_map(request)
wb = Workbook()
ws = wb.active
ws.title = "승인완료"
@@ -453,6 +446,20 @@ async def approved_export(
buf = io.BytesIO()
wb.save(buf)
buf.seek(0)
return buf
@router.get("/api/approved/export.xlsx")
async def approved_export(
request: Request,
user: dict[str, Any] = Depends(_require_approver),
) -> StreamingResponse:
store = _store(request)
_require_db_store(store)
year, mon, raw = _parse_month(request)
items = store.list_approved(year=year, month=mon)
names = _name_map(request)
buf = _build_approved_xlsx(items, names)
fname = f"expense_approved_{raw}.xlsx"
return StreamingResponse(
buf,
@@ -470,6 +477,7 @@ async def approved_attachments_zip(
store = _store(request)
_require_db_store(store)
year, mon, raw = _parse_month(request)
items = store.list_approved(year=year, month=mon)
atts = store.approved_attachments(year=year, month=mon)
names = _name_map(request)
upload_root = _upload_dir(request)
@@ -495,6 +503,10 @@ async def approved_attachments_zip(
buf = io.BytesIO()
used: dict[str, int] = {}
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
# 1) 승인 목록 엑셀 동봉
xlsx_buf = _build_approved_xlsx(items, names)
zf.writestr(f"승인목록_{raw}.xlsx", xlsx_buf.getvalue())
# 2) 첨부 파일들
for a in atts:
path = _resolve(a)
if path is None: