- 시험 615 통과 · 브라우저 화면 13개·메뉴 90개 오류 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
6.5 KiB
Python
159 lines
6.5 KiB
Python
# -*- 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}
|