feat(M02): 복사본 길 모양 맞춤 — 미리 가져오기 몸 {layer, owner, key}(이름은 층 SS 줄) · 목록에 원본판 · 입력연결은 sub4 층 읽기(_Inputs.read_file)로 (PLAN 25-2b)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt
This commit is contained in:
@@ -89,13 +89,16 @@ def list_items(project_root: str | Path) -> list[dict[str, Any]]:
|
||||
complete_old(child)
|
||||
manifest = read_json(child / MANIFEST_NAME) or {}
|
||||
origin = manifest.get("원본") or {}
|
||||
changed = source_changed(manifest)
|
||||
out.append(
|
||||
{
|
||||
"폴더": child.name,
|
||||
"이름": origin.get("name"),
|
||||
"원본": origin,
|
||||
"원본판": manifest.get("원본판"),
|
||||
"가져온날": manifest.get("가져온날"),
|
||||
"마스터바뀜": source_changed(manifest),
|
||||
"마스터바뀜": bool(changed),
|
||||
"원본없음": changed is None,
|
||||
"구조물": manifest.get("구조물") or [],
|
||||
"고침": bool(manifest.get("고침")),
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ from M02_MasterTemplete import M02_Template_Layers as layers
|
||||
TABLE_NAME = "구조물집계표"
|
||||
UM_FILE = "자재조합.json"
|
||||
UA_FILE = "일위대가조합.json" # M01 시스템(`resources/master_data/`)
|
||||
INPUTS_DIRNAME = "inputs" # 층의 항목 연결 파일 자리(25-1b · sub4 층 자료 틀)
|
||||
#: 열 id → 스냅샷 파일(구조물 도면 · 면적산출 · 재료산출)
|
||||
PART_FILES = (("structure", "도면.json"), ("area", "면적.json"), ("material", "재료.json"))
|
||||
LINK_FILE = "입력연결.json"
|
||||
@@ -55,6 +54,15 @@ def row_of(document: Any, key: str) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
|
||||
def item_name(item: dict[str, Any]) -> str:
|
||||
"""층 SS 줄의 보이는 이름(종류 · 분류1~3) — 없으면 LookupError."""
|
||||
row = row_of(read_json(layer_root(item) / layers.COMBO_FILE), str(item.get("key")))
|
||||
if row is None:
|
||||
raise LookupError(f"집계 항목 {item.get('key')} 이 {item.get('layer')} 층에 없음")
|
||||
parts = (row.get(k) for k in ("종류", "분류1", "분류2", "분류3"))
|
||||
return " ".join(str(p) for p in parts if p) or str(row["키"])
|
||||
|
||||
|
||||
def version_of(content: Any) -> str:
|
||||
raw = json.dumps(content, ensure_ascii=False, sort_keys=True).encode("utf-8")
|
||||
return hashlib.sha256(raw).hexdigest()[:16]
|
||||
@@ -88,8 +96,14 @@ def labor_rows(ua_key: str) -> dict[str, Any] | None:
|
||||
|
||||
|
||||
def input_links(root: Path, column_id: str) -> dict[str, Any]:
|
||||
"""층의 항목 연결 `inputs/<열id>.json` — 없으면 빈 연결(같은 모양)."""
|
||||
found = read_json(root / INPUTS_DIRNAME / f"{column_id}.json") if column_id else None
|
||||
"""층의 항목 연결 `inputs/<열id>.json`(sub4 `_Inputs.read_file`) · 없으면 빈 연결."""
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Inputs as inputs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
if not column_id:
|
||||
return {"판": None, "줄": []}
|
||||
with store.at(root):
|
||||
found = inputs.read_file(column_id)
|
||||
if isinstance(found, dict) and isinstance(found.get("줄"), list):
|
||||
return found
|
||||
return {"판": None, "줄": []}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""프로젝트 집계 항목 복사본 길 — `/api/projects/{id}/items…` (PLAN 25-1b · 25-2b · 25-2e).
|
||||
|
||||
GET …/items 목록(이름 · 원본 · 가져온날 · 마스터 바뀜)
|
||||
POST …/items {summary_item} 미리 가져오기(구조물에 안 이은 항목)
|
||||
POST …/items {layer, owner, key} 미리 가져오기(구조물에 안 이은 항목) → {폴더, 만듦, 가져온날}
|
||||
GET …/items/{폴더}/{종류} {종류, 판, 내용}
|
||||
PUT …/items/{폴더}/{종류} {판, 내용} — 판이 다르면 409 · 면적 · 재료는 서버 계산값
|
||||
POST …/items/{폴더}/refresh 새로 가져오기(입력값 · 고른자재는 같은 키면 살림)
|
||||
@@ -23,6 +23,7 @@ from common_util.common_util_auth import verify_session
|
||||
from common_util.common_util_summary_item import parse_summary_item
|
||||
from M02_MasterTemplete import M02_Item_Copy as copies
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Router_Layers as layer_routes
|
||||
from M02_MasterTemplete.M02_Item_Source import item_name
|
||||
|
||||
router = APIRouter(prefix="/api/projects", tags=["M02 Project Items"])
|
||||
|
||||
@@ -35,7 +36,13 @@ class WriteBody(BaseModel):
|
||||
|
||||
|
||||
class PrefetchBody(BaseModel):
|
||||
summary_item: dict[str, Any]
|
||||
"""`{layer, owner, key}`(이름은 층 SS 줄에서) — 옛 모양 `{summary_item}` 도 받음."""
|
||||
|
||||
layer: str | None = None
|
||||
owner: str | None = None
|
||||
key: str | None = None
|
||||
name: str | None = None
|
||||
summary_item: dict[str, Any] | None = None
|
||||
|
||||
|
||||
async def _root(session: dict[str, Any], project_id: str):
|
||||
@@ -66,10 +73,16 @@ async def list_items(
|
||||
async def prefetch_item(
|
||||
project_id: str, body: PrefetchBody, session: dict[str, Any] = Depends(verify_session)
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
item = parse_summary_item(body.summary_item)
|
||||
raw = body.summary_item or {"layer": body.layer, "owner": body.owner, "key": body.key}
|
||||
try: # 모양 먼저(이름은 자리만) — 그다음 층 SS 줄에서 이름
|
||||
item = parse_summary_item({**raw, "name": raw.get("name") or body.name or "-"})
|
||||
except ValueError as error:
|
||||
raise HTTPException(status_code=400, detail=str(error)) from error
|
||||
if not (raw.get("name") or body.name):
|
||||
try:
|
||||
item["name"] = item_name(item)
|
||||
except LookupError as error:
|
||||
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||
return await _run(copies.prefetch, await _root(session, project_id), item)
|
||||
|
||||
|
||||
|
||||
@@ -119,8 +119,10 @@ def test_지정하면_새_파일까지_복사(world):
|
||||
"폴더": FOLDER,
|
||||
"이름": "관공 관매설",
|
||||
"원본": PIPE,
|
||||
"원본판": manifest["원본판"],
|
||||
"가져온날": manifest["가져온날"],
|
||||
"마스터바뀜": False,
|
||||
"원본없음": False,
|
||||
"구조물": ["pipe@40.0"],
|
||||
"고침": False,
|
||||
}
|
||||
@@ -330,6 +332,11 @@ def test_길_권한_남의_프로젝트는_403(world, monkeypatch):
|
||||
assert client.get(f"{base}/{FOLDER}/모름").status_code == 422
|
||||
assert client.post(f"{base}/{FOLDER}/calc").json()["멈춤"]
|
||||
assert client.post(base, json={"summary_item": {"layer": "x"}}).status_code == 400
|
||||
made = client.post(base, json={"layer": "system", "owner": None, "key": "SS000022"}).json()
|
||||
assert made["폴더"] == "system_none_SS000022" and made["만듦"] is True
|
||||
names = {i["폴더"]: i["이름"] for i in client.get(base).json()["항목"]}
|
||||
assert names["system_none_SS000022"] == "돌쌓기 (찰·메마다) H=(높이마다)" # 층 SS 줄 이름
|
||||
assert client.post(base, json={"layer": "system", "key": "SS999999"}).status_code == 404
|
||||
session["company_id"] = 4
|
||||
assert client.get(base).status_code == 403
|
||||
assert client.post(f"{base}/{FOLDER}/refresh").status_code == 403
|
||||
|
||||
Reference in New Issue
Block a user