- DBF 고친 칸만 다시 씀(한글 깨짐 · 2칸을 1칸으로 · 3칸 87.745% 덮어쓰기 없앰) · 단가 → 내역 행 → 소계 → 직공비 다시 셈 · 행 끼우기·지우기 - SIxMemo(COSTN.SMT · PRJLST.SMT) 블록 구조 유지 · 넘치면 뒤에 붙이고 가리키는 값 갱신 - STC 다시 묶기(같은 파일은 원본 바이트) · 검사 · 자동 백업·복원 · 새 공사 · 복사등록(차수 인계) · 해당분 압축 - BDQTY 역변환(쓰기 준비) · 모듈 dbf_rw · dbf_memo · dbf_crypt · stc_archive · stc_save · stc_read · stc_project · api_file · scr_file - 지킴 시험 test_stmate_file_roundtrip · file_project · file_payload — 실무 STC 7건 왕복 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
4.1 KiB
Python
141 lines
4.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""파일 입출력 끼워 넣기 API — STC 검사 · 복사등록 · 해당분만 압축 · 새 공사 · 백업 목록/복원.
|
|
|
|
근거: 24_모듈구조_전수.md(ST_U_Archive Chk_STC_Ver·ComP_Data · wM_D_Restore) · 08번 §2 · 16번 §4.6 · 27번 §2
|
|
쓰기는 tools/stmate-test/work_dbf/ 아래만(server.WORK_DBF_ROOT).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
from stc_archive import build_stc, check_stc
|
|
from stc_project import (
|
|
copy_register,
|
|
list_backups,
|
|
new_project,
|
|
pack_selected,
|
|
safe_name,
|
|
write_output,
|
|
)
|
|
from stc_read import Source
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _root() -> pathlib.Path:
|
|
import server # 실행 중 바뀔 수 있는 작업 폴더(시험은 임시 폴더로 바꿈)
|
|
|
|
return server.WORK_DBF_ROOT
|
|
|
|
|
|
def _src(path: str) -> Source:
|
|
try:
|
|
return Source(path)
|
|
except FileNotFoundError:
|
|
raise HTTPException(404, f"경로 없음: {path}")
|
|
except ValueError as e:
|
|
raise HTTPException(400, str(e))
|
|
|
|
|
|
def _guard(fn, *a, **k):
|
|
try:
|
|
return fn(*a, **k)
|
|
except (ValueError, KeyError) as e:
|
|
raise HTTPException(400, str(e))
|
|
|
|
|
|
class PathReq(BaseModel):
|
|
source_path: str
|
|
|
|
|
|
class CopyRegReq(BaseModel):
|
|
source_path: str
|
|
src_pcode: str = "11"
|
|
mode: str = "copy" # copy · change · contract
|
|
pname: str = ""
|
|
rates: dict = {} # contract — {"M": %, "L": %, "S": %}
|
|
exclude_recnos: list[int] = []
|
|
target_folder_name: str = ""
|
|
|
|
|
|
class PackReq(BaseModel):
|
|
source_path: str
|
|
pcode: str = "11"
|
|
target_name: str = ""
|
|
|
|
|
|
class NewPrjReq(BaseModel):
|
|
template_path: str
|
|
pname: str
|
|
pnum: str = ""
|
|
target_folder_name: str = ""
|
|
|
|
|
|
class RestoreReq(BaseModel):
|
|
backup_path: str
|
|
target_folder_name: str
|
|
|
|
|
|
@router.post("/api/file/check")
|
|
def api_file_check(req: PathReq):
|
|
"""Chk_STC_Ver 대응 — ZIP·필수 엔트리·DBF 머리·SMT 서명·판 추정."""
|
|
s = _src(req.source_path)
|
|
b = s.path.read_bytes() if s.kind == "stc" else build_stc([], s.files)
|
|
return {"kind": s.kind, **check_stc(b)}
|
|
|
|
|
|
@router.post("/api/file/copy-register")
|
|
def api_file_copy_register(req: CopyRegReq):
|
|
s = _src(req.source_path)
|
|
files, pcode, changes = _guard(
|
|
copy_register,
|
|
s.files,
|
|
req.src_pcode,
|
|
req.mode,
|
|
req.pname,
|
|
req.rates,
|
|
req.exclude_recnos,
|
|
)
|
|
target = req.target_folder_name or f"{s.path.stem}_복사{pcode}"
|
|
names = dict(s.names, **{f"BOQ{pcode}.DBF": f"BOQ{pcode}.DBF"})
|
|
res = write_output(_root(), target, files, names, s.entries)
|
|
return {**res, "new_pcode": pcode, "change_count": len(changes)}
|
|
|
|
|
|
@router.post("/api/file/pack")
|
|
def api_file_pack(req: PackReq):
|
|
"""해당분만 압축 — 고른 공사(PCODE)만 담은 STC 를 work_dbf/ 에 만듦."""
|
|
s = _src(req.source_path)
|
|
files = _guard(pack_selected, s.files, req.pcode)
|
|
name = safe_name(req.target_name or f"{s.path.stem}_{req.pcode}")
|
|
out = _root() / f"{name}.STC"
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
out.write_bytes(build_stc(s.entries, files))
|
|
return {"ok": True, "saved_stc": str(out), "stc_size": out.stat().st_size}
|
|
|
|
|
|
@router.post("/api/file/new")
|
|
def api_file_new(req: NewPrjReq):
|
|
s = _src(req.template_path)
|
|
files = _guard(new_project, s.files, req.pname, req.pnum)
|
|
res = write_output(_root(), req.target_folder_name or req.pname, files, s.names, s.entries)
|
|
return res
|
|
|
|
|
|
@router.get("/api/file/backups")
|
|
def api_file_backups(name: str = ""):
|
|
return {"backups": list_backups(_root(), safe_name(name) if name else "")}
|
|
|
|
|
|
@router.post("/api/file/restore")
|
|
def api_file_restore(req: RestoreReq):
|
|
"""wM_D_Restore 대응 — 백업 STC 를 작업본으로 되돌림(지금 작업본은 다시 백업)."""
|
|
p = pathlib.Path(req.backup_path).resolve()
|
|
if _root().resolve() not in p.parents or not p.exists():
|
|
raise HTTPException(400, "작업 폴더 백업만 복원")
|
|
s = _src(str(p))
|
|
return write_output(_root(), req.target_folder_name, s.files, s.names, s.entries)
|