Files
Aislo/tools/stmate-test/conv_xls_apply.py
T

143 lines
6.1 KiB
Python

# -*- coding: utf-8 -*-
"""엑셀 → 프로젝트 반영 — 내역 불러오기(wM_From_XLS) · 역등록(CONV_XLS_2_ST · XLS_2_BDREC).
근거: conv_xls_boq.py · conv_xls_res.py(INITIAL 메타 복원 규칙) · STC_왕복검증.md §3(원본을 틀로) ·
16번 §4(소계·직접비 = PRJLST ?COSTn) · stc_project.new_project(빈 틀)
- 표 쓰기: 틀 DBF 의 필드 그대로 · 새 레코드 = 숫자 칸 0 · 논리 칸 빈칸 · 메모 칸 빈 포인터 · 주어진 칸만 씀
- 내역 불러오기: 고른 BOQ 표를 엑셀 행으로 통째로 바꿈 · BQCODE·EQCODE·단위는 그 프로젝트 COSTN 값 ·
PRJLST 직접비(?COSTn)는 소계 다시 셈(stc_save.recompute_boq) 결과
- 역등록: 틀 프로젝트(요율·서식·설정) + 목록표 COSTN + 산출근거 COSTN.SMT + 구성표 BDQTY + 내역 BOQ11 ·
PRJLST 공사명 = [공사정보] PNAME
- BDQTY 새 행 = 틀 BDQTY 의 같은 구성 코드 레코드를 옮겨 바뀐 칸만 암호로(stc_save_extra.save_bdqty)
"""
from __future__ import annotations
from conv_xls_boq import (
boq_from_xlsx,
boq_rows_from_sheet,
fill_from_costn,
find_sheet,
load_book,
read_initial,
)
from conv_xls_res import comp_from_san, comp_from_sheets, costn_from_lists, memos_from_sheet
from dbf_memo import BLANK_PTR, SixMemo, memo_text_bytes
from dbf_rw import DbfTable
from smt_parser import parse_dbf_bytes
from stc_project import new_project
from stc_save import SaveLog, pick_boq_table, prj_row_for, recompute_boq
from stc_save_extra import save_bdqty
def rows_to_table(template: bytes, rows: list[dict]) -> DbfTable:
"""틀 DBF 필드로 rows 를 담은 새 표(레코드 전부 새로 씀)."""
t = DbfTable(template)
zero = bytearray(b" " * t.rlen)
for f in t.fields.values():
if f.type in ("N", "F"):
zero[f.off : f.off + f.len] = t.encode_value(f.name, 0)
t.recs, t.dirty = [], True
for i, r in enumerate(rows):
t.recs.append(bytearray(zero))
for k, v in r.items():
if k.startswith("_") or k not in t.fields or t.fields[k].type == "M":
continue
if v is None or (isinstance(v, str) and not v.strip() and t.fields[k].type != "C"):
continue
t.set_raw(i, k, t.encode_value(k, v))
return t
def _direct_to_prj(files: dict, out: dict, bt: DbfTable, name: str, log: SaveLog) -> dict:
"""소계 행 다시 셈 + PRJLST 그 공사 행 ?COSTn = 차수별 직접비."""
slots = {s for s in "12345" if f"T_AMT{s}" in bt.fields}
direct = recompute_boq(bt, name, slots, log)
if "PRJLST.DBF" in files:
pt = DbfTable(out.get("PRJLST.DBF", files["PRJLST.DBF"]))
pi = prj_row_for(pt, name)
for s, vals in direct.items():
for c, v in zip(("MCOST", "LCOST", "SCOST", "TCOST"), vals):
if f"{c}{s}" in pt.fields:
log.set(pt, "PRJLST.DBF", pi, f"{c}{s}", v)
if pt.dirty:
out["PRJLST.DBF"] = pt.to_bytes()
return {s: [float(x) for x in v] for s, v in direct.items()}
def import_boq(files: dict, src, table: str = "", kind: str = "내역서") -> tuple[dict, dict]:
"""엑셀 내역 → 프로젝트 BOQ 표 통째 바꿈 — (새 files, 보고)."""
name = pick_boq_table(files, table)
if not name:
raise ValueError("BOQ 표 없음")
got = boq_from_xlsx(src, kind)
rows = got["rows"]
costn = parse_dbf_bytes(files["COSTN.DBF"])[1] if "COSTN.DBF" in files else []
filled = fill_from_costn(rows, costn)
known = {str(r.get("CODE", "")).strip() for r in costn}
unknown = sorted(
{
r["BDCODE1"]
for r in rows
if r["BDCODE1"][:1] in tuple("MLSXBDW") and r["BDCODE1"] not in known
}
)
out = dict(files)
log = SaveLog()
bt = rows_to_table(files[name], rows)
direct = _direct_to_prj(files, out, bt, name, log)
out[name] = bt.to_bytes()
return out, {
"table": name,
"sheet": got["sheet"],
"row_count": len(rows),
"costn_filled": filled,
"unknown_codes": unknown,
"direct": direct,
}
def _costn_table(template: bytes, rows: list[dict], memos: dict[str, str]) -> tuple[bytes, bytes]:
t = rows_to_table(template, rows)
memo = SixMemo(None)
if "IFORM" in t.fields:
for i in range(len(t)):
text = memos.get(t.get(i, "CODE"))
ptr = memo.write(BLANK_PTR, memo_text_bytes(text)) if text else BLANK_PTR
t.set_raw(i, "IFORM", ptr)
return t.to_bytes(), memo.to_bytes()
def register(template: dict, src, pname: str = "") -> tuple[dict, dict]:
"""역등록 — 엑셀 한 권으로 새 프로젝트 files 를 만듦(틀 = 다른 프로젝트의 요율·서식·설정)."""
wb = load_book(src)
info = read_initial(wb)
sec = info["sections"].get("공사정보", {})
name = pname or str((sec.get("PNAME") or ["엑셀 역등록 공사"])[0])
out = new_project(template, name)
costn = costn_from_lists(wb, info)
memos = memos_from_sheet(wb, info)
out["COSTN.DBF"], out["COSTN.SMT"] = _costn_table(template["COSTN.DBF"], costn, memos)
spec = find_sheet(info, "내역서")
if not spec or spec["sheet"] not in wb.sheetnames:
raise ValueError("INITIAL 목차에 내역서 시트 없음")
rows = boq_rows_from_sheet(wb[spec["sheet"]], spec["cols"])
fill_from_costn(rows, costn)
log = SaveLog()
bt = rows_to_table(template["BOQ11.DBF"], rows)
direct = _direct_to_prj(out, out, bt, "BOQ11.DBF", log)
out["BOQ11.DBF"] = bt.to_bytes()
comp = comp_from_sheets(wb, info) + comp_from_san(wb, info)
if comp and "BDQTY.DBF" in template:
base = template["BDQTY.DBF"] # 틀 레코드(같은 구성 코드)의 이름 암호 칸을 옮겨 씀
out["BDQTY.DBF"] = save_bdqty(base, comp, log, strict=False) or base
return out, {
"pname": name,
"costn_count": len(costn),
"memo_count": len(memos),
"boq_count": len(rows),
"bdqty_count": len(comp),
"direct": direct,
"sheets": {k: v["sheet"] for k, v in info["sheets"].items()},
}