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

159 lines
6.5 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 -*-
"""공사(내역서) 삭제·단계 등록(차수) 끼워 넣기 API — 공사목록표(wPrjList) 「내역서 삭제」(Act_Del_PrjExecute · ST_U_DelPrj Delete_Prj).
근거: REA 09 resource-TWPRJLIST 메뉴 「내역서 삭제」 · old/30_원자료 module_exports(ST_U_DelPrj: Delete_Prj)
· 02번(PRJLST 한 행 = 공사 · PCODE '1n' ↔ BOQ1n.DBF · PRJ_MEMO = 그 공사 원가 설정)
규칙: 고른 PCODE 의 PRJLST 행과 BOQ1n.DBF 를 뺌 · 다른 공사 행·표·메모는 바이트 그대로 · 마지막 한 공사는 지우지 않음
(원본 확인 문구·선택지는 근거 없음 — 화면은 확인 창 한 번)
단계 등록(NewProject → MkBoq · REA 31 Main_K_ORD_registration_transition_matrix · 최종보고서 §4.1):
원본 PCLASS × 등록(6 기성 · 7 설계변경) × 플래그(0 차수별 · 1 당초/변경) → 새 K_ORD(1 또는 K+1) · 상한 없음
새 PCLASS = 등록 번호(4 계약 · 6 기성 · 7 설계변경 갈래와 같은 수 — 추정)
K+1 = 다음 차수 칸 열기(stc_project change) · 1 = 원본 마지막 차수 칸을 1·2 칸(당초·변경)으로 옮겨 새로 시작
"""
from __future__ import annotations
import pathlib
from dbf_rw import DbfTable
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from stc_project import SLOT_COL, copy_register, safe_name, write_output
from stc_read import Source
from stc_save import SaveLog, recompute_boq
router = APIRouter()
def _root() -> pathlib.Path:
import server # 실행 중 바뀔 수 있는 작업 폴더(시험은 임시 폴더로 바꿈)
return server.WORK_DBF_ROOT
def delete_project(files: dict, pcode: str) -> tuple[dict, list]:
"""(새 files, 바뀐 목록) — PRJLST 행 · BOQ{pcode}.DBF 뺌."""
pcode = str(pcode).strip()
if "PRJLST.DBF" not in files:
raise ValueError("PRJLST.DBF 없음")
prj = DbfTable(files["PRJLST.DBF"])
idx = [i for i in range(len(prj)) if prj.get(i, "PCODE") == pcode]
if not idx:
raise ValueError(f"PRJLST 에 PCODE {pcode} 공사 없음")
if len(prj) - len(idx) < 1:
raise ValueError("마지막 공사는 지울 수 없음")
log = [
{"table": "PRJLST.DBF", "recno": i, "field": "_delete", "old": pcode, "new": ""}
for i in idx
]
prj.recs = [r for i, r in enumerate(prj.recs) if i not in set(idx)]
prj.dirty = True
out = dict(files)
out["PRJLST.DBF"] = prj.to_bytes()
tbl = f"BOQ{pcode}.DBF"
if out.pop(tbl, None) is not None:
log.append({"table": tbl, "recno": -1, "field": "_drop", "old": tbl, "new": ""})
return out, log
class DelPrjReq(BaseModel):
source_path: str
pcode: str
target_folder_name: str = ""
@router.post("/api/ui/project-delete")
def api_project_delete(req: DelPrjReq):
try:
s = Source(req.source_path)
files, log = delete_project(s.files, req.pcode)
except (FileNotFoundError, ValueError) as e:
raise HTTPException(400, str(e))
target = req.target_folder_name or s.path.stem
names = {k: v for k, v in s.names.items() if k in files}
res = write_output(_root(), safe_name(target), files, names, s.entries)
tbl = f"BOQ{str(req.pcode).strip()}.DBF"
left = pathlib.Path(res["saved_dir"]) / s.names.get(tbl, tbl) # 같은 작업본 폴더에 남은 옛 표
if tbl not in files and left.exists():
left.unlink()
return {**res, "deleted": req.pcode, "changes": log}
# 원본 PCLASS × 등록 × 플래그 → 새 K_ORD("1" | "K+1") — 표에 없는 원본(설계 0 등)은 다음 차수(K+1)
K_ORD_RULE = {
(4, 6, None): "1",
(6, 6, None): "K+1",
(7, 6, None): "1",
(4, 7, None): "1",
(6, 7, None): "1",
(7, 7, 0): "K+1",
(7, 7, 1): "1",
}
def new_k_ord(pclass: int, op: int, flag: int, k: int) -> int:
rule = K_ORD_RULE.get((pclass, op, flag)) or K_ORD_RULE.get((pclass, op, None)) or "K+1"
return k + 1 if rule == "K+1" else 1
def _move_slot(tbl: DbfTable, src: str, dst: tuple[str, ...]) -> None:
pairs = []
for c in tbl.order:
m = SLOT_COL.match(c)
if m and m.group(2) == src:
pairs += [(c, f"{m.group(1)}{d}") for d in dst if f"{m.group(1)}{d}" in tbl.fields]
for i in range(len(tbl)):
for a, b in pairs:
tbl.set_raw(i, b, tbl.raw(i, a))
def stage_register(files: dict, src_pcode: str, op: int, flag: int, pname: str = ""):
"""(새 files, 새 PCODE, 새 K_ORD) — 단계 등록."""
prj = DbfTable(files["PRJLST.DBF"])
si = next(i for i in range(len(prj)) if prj.get(i, "PCODE") == str(src_pcode))
num = lambda f: int(float(prj.get(si, f) or 0)) if f in prj.fields else 0 # noqa: E731
k, pclass = num("K_ORD"), num("PCLASS")
nk = new_k_ord(pclass, int(op), int(flag), k)
out, pcode, _log = copy_register(
files, str(src_pcode), "change" if nk == k + 1 else "copy", pname
)
prj2 = DbfTable(out["PRJLST.DBF"])
ni = next(i for i in range(len(prj2)) if prj2.get(i, "PCODE") == pcode)
prj2.set(ni, "K_ORD", nk)
if "PCLASS" in prj2.fields:
prj2.set(ni, "PCLASS", int(op))
if nk == 1 and k + 1 != 1: # 원본 마지막 차수(k+1) 칸 → 새 공사 당초·변경(1·2) 칸
name = f"BOQ{pcode}.DBF"
bt = DbfTable(out[name])
_move_slot(bt, str(k + 1), ("1", "2"))
direct = recompute_boq(bt, name, {"1", "2"}, SaveLog())
for s, vals in direct.items():
for c, v in zip(("MCOST", "LCOST", "SCOST", "TCOST"), vals):
if f"{c}{s}" in prj2.fields:
prj2.set(ni, f"{c}{s}", v)
out[name] = bt.to_bytes()
out["PRJLST.DBF"] = prj2.to_bytes()
return out, pcode, nk
class StageRegReq(BaseModel):
source_path: str
src_pcode: str = "11"
op: int = 7 # 6 기성 · 7 설계변경
flag: int = 0 # 0 차수별 · 1 당초/변경
pname: str = ""
target_folder_name: str = ""
@router.post("/api/ui/stage-register")
def api_stage_register(req: StageRegReq):
try:
s = Source(req.source_path)
files, pcode, nk = stage_register(s.files, req.src_pcode, req.op, req.flag, req.pname)
except (FileNotFoundError, ValueError, StopIteration) as e:
raise HTTPException(400, str(e) or "공사 없음")
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(), safe_name(target), files, names, s.entries)
return {**res, "new_pcode": pcode, "k_ord": nk}