Files
Aislo/tools/stmate-test/conv_jaje.py
T
eomsangdonandClaude Opus 5.5 47a04b6df8 feat(stmate-test): REA MCP(Ghidra) 파일 입출력 — JDL 탭 줄·묶음 내보내기·검사 · 구버전→Win8 변환 · 엑셀 역등록 구성 줄 · 자재 집계표 엑셀 · 그림 코드
- 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>
2026-11-14 15:58:17 +09:00

107 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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)