feat(m02): 일위대가 사전조합 자재목록 — 서고 모드 막힘 세 곳 풂 · 저장이 고름 · 자재표시 · 자재 살림 (175-1)
- 화면: 사전조합도 구조물 일위대가와 같은 자재목록 부품(자재 추가 · 세 칸 · 운반비) · getDoc 이 자재를 안 지움 - 서버: _combo_lines 가 고름 · 자재표시 남김 · clean_doc 에 자재(labor.clean 검사 · 로직 자재와 같은 품목 400 = no_twice 한 벌) - 시험: test_175_precombo_mats(저장 왕복 · 400) · test_m02_labor_mat_marks 단정 고침 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
This commit is contained in:
@@ -607,8 +607,13 @@ def server(doc: Any) -> dict[str, Any]:
|
||||
사용자 자재가 로직 자재 품목과 같으면 400(103-1 · 같은 품목 두 번 셈)."""
|
||||
got = clean(doc)
|
||||
out = calc(got)
|
||||
no_twice(got, out)
|
||||
return {**got, "계산값": out}
|
||||
|
||||
|
||||
def no_twice(got: dict, out: dict) -> None:
|
||||
"""사용자 자재가 로직 자재 품목과 같으면 400 — 일위대가 · 사전조합(175-1) 저장 한 벌."""
|
||||
inside = {x["품목"]["키"] for one in out["줄"] for x in one.get("재료") or [] if x.get("품목")}
|
||||
twice = sorted({m["코드"] for m in got["자재"]} & inside)
|
||||
if twice:
|
||||
raise _bad(f"자재 「{', '.join(twice)}」 는 로직 자재에 있음 — 같은 품목 두 번 담음")
|
||||
return {**got, "계산값": out}
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
층 문서를 읽을 때 옛 참조 줄 = 로직 줄 + `승격`(`unpromote`) · 프로젝트 사본은 그대로 셈
|
||||
precombo `{층}/precombo/SC-######.json` 사전조합
|
||||
`{종류:"사전조합", 키, 이름, 단위, 본뜬, 담은로직:[{참조, 수량:{손}, 메모}
|
||||
| {로직, 입력값, 메모, 수량:{손}}]}` — 승격 · 사전조합 · 단가산출 로직(51-11) ·
|
||||
수량은 손(기본 1) · 값 = Σ 안 줄 1 단위 값 × 수량(로직 줄 = 기준 ÷ 배)
|
||||
| {로직, 입력값, 메모, 수량:{손}, 고름?, 자재표시?}], 자재:[일위대가 자재 꼴]}` —
|
||||
승격 · 사전조합 · 단가산출 로직(51-11) · 수량은 손(기본 1) · 자재 수량은 손 · 빈칸 = 안 셈(175) ·
|
||||
값 = Σ 안 줄 1 단위 값 × 수량(로직 줄 = 기준 ÷ 배) + Σ 자재 기준단가 × 손
|
||||
참조 = `{층, 주인, 키}`(마스터 주인 null · 회사 `C` · 개인 `C/U`) — 종류는 키 머리(SP · SC) ·
|
||||
위 층만 가리킴(개인 → 그 개인 · 그 회사 · 마스터 / 회사 → 그 회사 · 마스터 / 마스터 → 마스터) ·
|
||||
같은 참조 두 번 · 순환(422)은 저장 때 막음 · 계산 때도 깊이 상한(`DEPTH`)에서 멈추고 까닭.
|
||||
@@ -142,6 +143,7 @@ def clean_doc(kind: str, doc: Any) -> dict[str, Any]:
|
||||
if not name:
|
||||
raise _bad("사전조합 이름이 빔")
|
||||
out.update(이름=name, 단위=unit or None, 담은로직=_combo_lines(doc, key))
|
||||
out["자재"] = _combo_mats(doc, out["담은로직"])
|
||||
out.update({k: str(doc.get(k) or "").strip() for k in ("구분", "상세구분")})
|
||||
out["본뜬"] = doc["본뜬"] if isinstance(doc.get("본뜬"), dict) else None
|
||||
return out
|
||||
@@ -206,7 +208,7 @@ def _combo_lines(doc: dict, key: str) -> list[dict[str, Any]]:
|
||||
head = {"참조": one["참조"]} if "참조" in one else {"로직": one["로직"]}
|
||||
if "로직" in one:
|
||||
head["입력값"] = one["입력값"]
|
||||
head.update({k: one[k] for k in ("켜쌓기율",) if k in one})
|
||||
head.update({k: one[k] for k in ("켜쌓기율", "고름", "자재표시") if k in one})
|
||||
lines.append({**head, "수량": one["수량"] or {"손": 1}, "메모": one["메모"]})
|
||||
at_ = here()
|
||||
me = {"층": at_[0], "주인": at_[1], "키": key}
|
||||
@@ -219,6 +221,18 @@ def _combo_lines(doc: dict, key: str) -> list[dict[str, Any]]:
|
||||
return lines
|
||||
|
||||
|
||||
def _combo_mats(doc: dict, lines: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""사전조합 자재(175-1) — 일위대가 자재와 같은 검사(`labor.clean` · 개인 자재 · 세 칸) ·
|
||||
로직 자재와 같은 품목 400(`labor.no_twice`)."""
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
mats = labor.clean({"자재": doc.get("자재") or []})["자재"]
|
||||
if mats:
|
||||
got = {"담은로직": lines, "자재": mats}
|
||||
labor.no_twice(got, labor.calc(got, {}))
|
||||
return mats
|
||||
|
||||
|
||||
def _no_loop(ref: Ref, trail: list[Ref]) -> None:
|
||||
"""저장 때 순환 · 깊이 — 아래로 따라가 처음 문서를 다시 만나면 422 「A → B → A」."""
|
||||
if len(trail) > DEPTH:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* · 자재목록 = 로직 자재 줄(103-1 · `Labor_LogicMats` · [바꾸기] → `담은로직[].고름` · 입력이 바뀌면 걸린 고름 풂) +
|
||||
* 사용자 자재 줄([삭제]) · 로직 자재와 같은 품목은 사용자 자재로 못 담음
|
||||
* · 오른쪽 「원문」 접는 패널(47-4 · 상세만 · 공용 `ui_template_side_panels`) = 고른 줄 로직의 출처 원문
|
||||
* · 서고 모드(50-3 · `mode`) = 사전조합 — 이름 칸 · 단가산출 로직 표 · 원문만(자재 · 면적 · 다이어그램 없음) ·
|
||||
* · 서고 모드(50-3 · `mode`) = 사전조합 — 이름 칸 · 단가산출 로직 표 · 자재목록(175-1) · 원문(면적 · 다이어그램 없음) ·
|
||||
* 정보 칸 구분 · 상세구분(51-12 · 고름) · 일위대가 상세와 같은 [로직 추가] · 표 · 동작(51-11) · 수량 손 입력(기본 1)
|
||||
* · 로직 줄 구분 드롭다운(134-1 · 단가산출 | 단가승격 · 서고 모드엔 없음)
|
||||
* ========================================================================== */
|
||||
@@ -228,13 +228,13 @@ export function mountLaborDetail(
|
||||
cur.담은로직,
|
||||
calc ?? last,
|
||||
!calc && !fail,
|
||||
// 세 칸(148-4) = 구조물 일위대가만(사전조합 줄은 `_combo_lines` 가 안 남김)
|
||||
// 세 칸(148-4) — 사전조합도 같음(175-1 · `_combo_lines` 가 남김)
|
||||
edit
|
||||
? {
|
||||
open: openRow,
|
||||
swap: swapMat,
|
||||
reset: resetMat,
|
||||
mark: lib ? undefined : markLogicMat,
|
||||
mark: markLogicMat,
|
||||
}
|
||||
: { open: openRow },
|
||||
),
|
||||
@@ -641,7 +641,7 @@ export function mountLaborDetail(
|
||||
]),
|
||||
// 사전조합 = 같은 [로직 추가](51-11)
|
||||
section(tc("Logics_Title"), opt.card ? null : addLogic, logicHost),
|
||||
...(lib ? [] : [section(tc("Labor_Mats"), opt.card ? null : addMat, matHost)]),
|
||||
section(tc("Labor_Mats"), opt.card ? null : addMat, matHost),
|
||||
...(opt.card || lib ? [] : [dgBox]),
|
||||
],
|
||||
}),
|
||||
@@ -668,12 +668,11 @@ export function mountLaborDetail(
|
||||
});
|
||||
|
||||
return {
|
||||
// 서고 모드 = 문서 종류 그대로 · 열 · 자재 없음
|
||||
// 서고 모드 = 문서 종류 그대로 · 열 없음
|
||||
getDoc: () => {
|
||||
if (!lib) return cur;
|
||||
const out: Record<string, unknown> = { ...cur, 종류: "사전조합" };
|
||||
delete out.열;
|
||||
delete out.자재;
|
||||
return out as unknown as LaborDoc;
|
||||
},
|
||||
saveArea: async () => {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""M02 일위대가 사전조합 자재목록(PLAN 175) — 구조물 일위대가와 같은 자재목록.
|
||||
|
||||
175-1 화면 · 저장 — 서고 모드 자재목록 · 로직 자재 세 칸 막힘 풂 · 저장 `_combo_lines` 가
|
||||
고름 · 자재표시 남김 · 문서 `자재`(일위대가 자재 검사 · 로직 자재와 같은 품목 400).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 임시 층 fixture
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SRC = os.path.join(HERE, "..", "..", "M02_MasterTemplete")
|
||||
NET = "GC000337" # 낙석방지망 — 재료 줄 3 · 4 · 5(철망 · 결속선 · 에폭시)
|
||||
NET_ITEM = "MT006697"
|
||||
WIRE = "MT000194"
|
||||
MAT = "MT000008" # 자재품목 · 물가정보 83000
|
||||
|
||||
|
||||
def _save(folder, doc):
|
||||
with store.at(folder):
|
||||
key = refs.create("precombo", {"이름": "자재 조합", **doc})["이름"]
|
||||
return key, json.loads((folder / "precombo" / f"{key}.json").read_bytes())
|
||||
|
||||
|
||||
def test_화면_서고_모드도_자재목록_세_칸_자재_저장(world): # noqa: F811
|
||||
detail = open(os.path.join(SRC, "M02_MasterTemplete_Labor_Detail.ts"), encoding="utf-8").read()
|
||||
assert 'section(tc("Labor_Mats"), opt.card ? null : addMat, matHost),' in detail
|
||||
assert 'lib ? [] : [section(tc("Labor_Mats")' not in detail
|
||||
assert "mark: markLogicMat," in detail and "lib ? undefined" not in detail
|
||||
assert "delete out.자재" not in detail and "delete out.열" in detail
|
||||
|
||||
|
||||
def test_저장_왕복_고름_자재표시_자재_남음(world): # noqa: F811
|
||||
line = {"로직": NET, "고름": {"4": NET_ITEM}, "자재표시": {"3": {"자재구분": "관급"}}}
|
||||
mats = [
|
||||
{"코드": MAT, "수량": {"손": 2}, "운반비": "미포함", "이름": "틀린 이름"},
|
||||
{"코드": WIRE, "수량": None},
|
||||
]
|
||||
_, got = _save(world["system"], {"담은로직": [line], "자재": mats})
|
||||
one = got["담은로직"][0]
|
||||
assert one["고름"] == {"4": NET_ITEM} and one["자재표시"] == {"3": {"자재구분": "관급"}}
|
||||
assert [(m["코드"], m["수량"]) for m in got["자재"]] == [(MAT, {"손": 2}), (WIRE, None)]
|
||||
assert got["자재"][0]["운반비"] == "미포함" and got["자재"][0]["이름"] != "틀린 이름"
|
||||
# 자재 없음 = 빈 목록(옛 문서 꼴 그대로 읽힘)
|
||||
_, empty = _save(world["system"], {"담은로직": [{"로직": NET}]})
|
||||
assert empty["자재"] == []
|
||||
|
||||
|
||||
def test_로직_자재와_같은_품목_400_없는_자재_400(world): # noqa: F811
|
||||
with pytest.raises(store.StoreError) as e:
|
||||
_save(world["system"], {"담은로직": [{"로직": NET}], "자재": [{"코드": WIRE}]})
|
||||
assert e.value.status == 400 and WIRE in str(e.value.detail)
|
||||
with pytest.raises(store.StoreError) as e:
|
||||
_save(world["system"], {"담은로직": [], "자재": [{"코드": "MT999999"}]})
|
||||
assert e.value.status == 400
|
||||
@@ -125,7 +125,7 @@ def test_화면_칸_부품_사용자_줄_로직_줄_같은_칸():
|
||||
assert "...markCells(mat, on?.mark" in rows and "...markHeads()," in rows
|
||||
assert "...markCells(" in _src("M02_MasterTemplete_Labor_LogicMats.ts")
|
||||
detail = _src("M02_MasterTemplete_Labor_Detail.ts")
|
||||
assert "mark: lib ? undefined : markLogicMat" in detail
|
||||
assert "mark: markLogicMat," in detail and "lib ? undefined" not in detail # 사전조합도(175-1)
|
||||
assert "mark: markMat" in detail
|
||||
# 서버 허용 값 = 화면 허용 값
|
||||
api = _src("M02_MasterTemplete_Labor_Api.ts")
|
||||
|
||||
Reference in New Issue
Block a user