# -*- coding: utf-8 -*- """재료비 수량집계표를 엑셀파일로 만들기(wM_JajeXLS · 단방향 내보내기) — 「주요건설자재 현황」 시트. 근거(지식DB REA 분석/37 §3): STmate.Cpm TWM_JAJEXLS DFM 0x698df0(입력 8칸 · 「엑셀파일 만들기」·「종료」) · ActMkJaje 0x5658bc(CON_QTY 의 B_CODE 첫 글자 M 줄 → PRT) · ST_P_XLS Prn_Col_M_EXL 0x4eb4d0 · 머리 0x4ea93c · 2~5줄 공사 정보 · 7줄 머리글(Start8x.Ini [PRINT] CAP_* · 없으면 기본 글) · 열 너비 31·31·12·6·12·14·12 · 줄: 명칭(+줄바꿈+NAME2) · 규격(+줄바꿈+SIZE2) · 수량 = 소수 2자리 사사오입 · 단위 · 단가 · 금액 = ROUND(수량×단가, 0) · 비고 · 65,000 줄 넘으면 새 시트 조건부: 1줄 제목 자리 · 「단위/수량」 자리 바꿈 설정(+0x529c) · 셀 병합 범위 · 파일 이름(원본 기본값 미확인) """ from __future__ import annotations import io from decimal import ROUND_HALF_UP, Decimal TITLE = "주요건설자재 현황" SHEET = "주요건설자재현황" CAPS = [ ("CAP_NAME", "명 칭"), ("CAP_SIZE", "규 격"), ("CAP_QTY", "수 량"), ("CAP_SUNIT", "단위"), ("CAP_AMT", "단 가"), ("CAP_AMOUNT", "금 액"), ("CAP_BIGO", "비 고"), ] WIDTHS = [31, 31, 12, 6, 12, 14, 12] MAX_ROWS = 65000 def half(v: float, d: int = 0) -> float: q = Decimal(1).scaleb(-d) return float(Decimal(str(v)).quantize(q, rounding=ROUND_HALF_UP)) def caps_from_ini(sections: dict) -> list[str]: prt = {k.upper(): v for k, v in (sections.get("PRINT") or {}).items()} return [prt.get(k) or d for k, d in CAPS] def _join(a: str, b: str) -> str: a, b = (a or "").rstrip(), (b or "").rstrip() return f"{a}\n{b}" if b else a def build_jaje_xlsx(head: dict, rows: list[dict], caps: list[str] | None = None) -> bytes: """head: PNAME · PNUM · BUDGET · 수요기관 · 시공회사 · 공사현장 · 공사기간 · 계약일자 / rows: NAME · NAME2 · SIZE · SIZE2 · UNIT · QTY · PRICE(차수 합계 단가).""" import openpyxl from openpyxl.styles import Alignment, Font wb = openpyxl.Workbook() wb.remove(wb.active) caps = caps or [d for _k, d in CAPS] chunks = [rows[i : i + MAX_ROWS] for i in range(0, len(rows), MAX_ROWS)] or [[]] for n, part in enumerate(chunks): ws = wb.create_sheet(SHEET if n == 0 else f"{SHEET}{n + 1}") for j, w in enumerate(WIDTHS): ws.column_dimensions[chr(65 + j)].width = w ws["A1"] = TITLE ws["A1"].font = Font(bold=True, size=14) ws.merge_cells("A1:G1") ws["A1"].alignment = Alignment(horizontal="center") ws["A2"] = f"공 사 명: {head.get('PNAME', '')}" ws["G2"] = f"관리번호: {head.get('PNUM', '')}" for r, (la, va, lb, vb) in enumerate( [ ("수 요 기 관", head.get("수요기관", ""), "시 공 회 사", head.get("시공회사", "")), ("예 산 액", head.get("BUDGET", ""), "공 사 현 장", head.get("공사현장", "")), ("공 사 기 간", head.get("공사기간", ""), "계 약 일 자", head.get("계약일자", "")), ], start=3, ): ws.cell(r, 1, la) ws.cell(r, 3 if r == 4 else 2, va) if r != 4: ws.merge_cells(f"B{r}:C{r}") ws.cell(r, 4, lb) ws.merge_cells(f"D{r}:E{r}") ws.cell(r, 6, vb) ws.merge_cells(f"F{r}:G{r}") for j, c in enumerate(caps, 1): ws.cell(7, j, c).alignment = Alignment(horizontal="center") for i, r in enumerate(part, start=8): qty = half(float(r.get("QTY") or 0), 2) price = float(r.get("PRICE") or 0) ws.cell(i, 1, _join(r.get("NAME", ""), r.get("NAME2", ""))).alignment = Alignment( wrap_text=True ) ws.cell(i, 2, _join(r.get("SIZE", ""), r.get("SIZE2", ""))).alignment = Alignment( wrap_text=True ) ws.cell(i, 3, qty) ws.cell(i, 4, r.get("UNIT", "")) ws.cell(i, 5, price) ws.cell(i, 6, half(qty * price, 0)) ws.cell(i, 7, "") out = io.BytesIO() wb.save(out) return out.getvalue() def amount(row: dict) -> float: """금액 = ROUND(수량(소수 2자리 사사오입) × 단가, 0).""" return half(half(float(row.get("QTY") or 0), 2) * float(row.get("PRICE") or 0), 0)