- JDL 줄 = 탭 구분 + \x05/\x07 덧붙임(검사기 0x504d20 · 고정폭 표는 죽은 고리 — 34 §17.9 바로잡음) · 단가 목록 7종·Main·GongJong 머리·Global.Ini 상수 키 · zip 받기 - 구버전 판 가리기(Chk_Data 0x459280) · 7.x/DOS 표별 필드 대응 · B→X 조건 = 단위 X - 엑셀 역등록 PRPT「속성」·PRIN「대상」·PROUT「저장」 = PR/RR 줄만 · 원본 BDQTY 4건 일치 - 자재 = 재료비 수량집계표 엑셀 내보내기(주요건설자재현황 시트) - 그림 코드 = COSTN I 코드 IFORM · SIZE = 그림 클래스 이름 - 지식DB 37번 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
107 lines
4.4 KiB
Python
107 lines
4.4 KiB
Python
# -*- 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)
|