feat(723-4): 723-1 뒤 B09 일위대가 수량 연결 마무리 — 알약 값 쓴 SC 만 덮개 나눔 · SK 속 SC · 공사 불러오기 수량 · 비고 경고
- Labor_Source = 덮개 열쇠 `C:참조|S:사본` 은 알약 값 쓴 SC 블록만(빈 수량 · 옛 손 = `C:참조` 그대로 = 703-1 지난 공사 가져옴 꼴) · SK 속 SC 안 줄 수량 없음 = 엔진엔 0 값묶음(`_Loose`) · 그 줄 = 사전조합 블록(이 공사 입력) — SK 통째 빈칸 안 됨 - Bill_Hopyo(707-7 공사 통째 불러오기) = 이 공사 엑셀 수량을 굳힌 손으로 남김(723-1 `with_pills` 수량 걷기는 M02 저장용) - Daega_Personal 원천 = 블록 origin 참조(덮개 열쇠 꼬리 무관) · 내 자료집 저장 알림 글 = 줄 수량 저장 안 함 - 비고(펼침 · 일위대가표) = 수량 없는 SC 안 줄은 「산근 n호표 · 수량 없음 — 이 공사에서 입력」(`noteOf`) - 낡은 시험 고침(사유 = 723-1 SC 안 줄 수량 저장 안 함 · 이 공사 입력 · 알약으로 이음): test_204_2(서고 SC 수량 없음 · ask) · test_703(자료집 저장 손 걷힘) · test_719_1(서고 사본 = 알약 값으로 같은 값 · 없으면 수량 없음) · test_719_2(SK 속 SC = 빈 줄 → 이 공사 입력 1 = M02 알약 1 금액) · test_702_3(서고 SC 빈 줄 3 → 입력 뒤 금액) - test_723_4 = 구조물 사본 면적 알약 굳힘 · 설계 연동 · 알약 안 이음 = 빈 줄 시험 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ if str(ROOT) not in sys.path:
|
||||
from B09_Estimation import B09_Estimation_Bill_Library as library # noqa: E402
|
||||
from B09_Estimation import B09_Estimation_Router as router # noqa: E402
|
||||
from B09_Estimation import B09_Estimation_Store as store_b09 # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import ASK, daega_inputs # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Linked import linked_resource_keys # noqa: E402
|
||||
from config import config_system # noqa: E402
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs # noqa: E402
|
||||
@@ -62,7 +62,9 @@ def world(tmp_path, monkeypatch):
|
||||
company = layers.company_dir(7)
|
||||
company.mkdir(parents=True)
|
||||
with store.at(company):
|
||||
other = refs.create("precombo", {"이름": "회사 조합", "단위": "㎡", "담은로직": lines[:1]})["이름"]
|
||||
other = refs.create("precombo", {"이름": "회사 조합", "단위": "㎡", "담은로직": lines[:1]})[
|
||||
"이름"
|
||||
]
|
||||
project = tmp_path / "storage" / "7" / "1" / "proj"
|
||||
project.mkdir(parents=True)
|
||||
return {"system": system, "key": key, "company": other, "root": project}
|
||||
@@ -84,7 +86,10 @@ def test_사전조합_고름_사본_들여옴_새_호표_다시_고르면_재사
|
||||
got, block = _block(root, folder)
|
||||
assert (block["kind"], block["name"], block["unit"]) == ("사전조합", "서고 조합", "㎡")
|
||||
kinds = [(ln["kind"], ln.get("qty")) for ln in block["lines"]]
|
||||
assert kinds == [("sangeun", 2.0), ("sangeun", 1.0), ("material", 3.0)]
|
||||
# 723-1 · 723-4 = M02 SC 안 줄 수량 저장 안 함 → 내역에서 바로 쓴 SC = 수량 없음(이 공사에서 입력)
|
||||
assert kinds == [("sangeun", None), ("sangeun", None), ("material", None)]
|
||||
assert all(ln["ask"] == ASK for ln in block["lines"])
|
||||
assert block["cover"] == f"C:{block['origin']['참조']}" # 공사 안 한 덮개 그대로
|
||||
assert got["owners"] == {} # 구조물 사본 없어도 호표
|
||||
# 단가 묶음 = 담은 로직 · 사전조합 자재 · 고른 자재(사본 셈이 마스터를 안 읽음)
|
||||
bundle = json.loads((copy / "단가.json").read_text("utf-8"))
|
||||
|
||||
@@ -44,6 +44,10 @@ def test_서고_사본_산근_목록에_듦_대가_단가_채움(world):
|
||||
by_key = {r["key"]: r for r in sg["sg_list"]}
|
||||
labor = compute_labor(daega, {**base, "gaps": []}, by_key)
|
||||
row = next(r for r in labor["daega"] if r["key"] == block["id"])
|
||||
assert not row["sum"] and row["blank"] == 3 # 723-4 서고 SC 안 줄 수량 없음 = 이 공사에서 입력
|
||||
cover = {block["cover"]: {ln["cid"]: {"qty": q} for ln, q in zip(block["lines"], (2, 1, 3))}}
|
||||
labor = compute_labor(daega, {**base, "gaps": []}, by_key, cover)
|
||||
row = next(r for r in labor["daega"] if r["key"] == block["id"])
|
||||
assert row["sum"] and not row.get("blank")
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
- 복사본 = 덮개 `K:…` 의 `@copy` {from · name · spec} — 셈 짝(`Calc_Labor` 파이썬 · TS 거울)이
|
||||
원본 블록을 본떠 끝 번호 새 호표 · 덮개 = 제 것 · 원본 없음 = 짝 잃음 ·
|
||||
저장 다듬기 · 서고 사본 지킴(prune)
|
||||
- 내 자료집 = 개인 층 사전조합(임시 storage) — 줄 수량 덮개 = 손 · 품 · 단가 덮개 = 안 담음 수 ·
|
||||
- 내 자료집 = 개인 층 사전조합(임시 storage) — 줄 수량 덮개 = 문서 손(쓸 때 걷힘 · 723-1) ·
|
||||
품 · 단가 덮개 = 안 담음 수 ·
|
||||
같은 이름 409 → 덮어쓰기 · 새 이름
|
||||
"""
|
||||
|
||||
@@ -178,7 +179,8 @@ def test_자료집_같은_이름_물음(world):
|
||||
saved = json.loads(
|
||||
(world["folder"] / "precombo" / f"{first['이름']}.json").read_text(encoding="utf-8")
|
||||
)
|
||||
assert saved["이름"] == "내 돌붙임" and saved["담은로직"][1]["수량"] == {"손": 9}
|
||||
# 723-1 = M02 SC 안 줄 수량 저장 안 함(덮어쓴 손 9 도 걷힘 · 쓰는 곳 알약 · 이 공사 입력으로 이음)
|
||||
assert saved["이름"] == "내 돌붙임" and saved["담은로직"][1]["수량"] is None
|
||||
assert saved["담은로직"][0]["입력값"] == b145.STONE
|
||||
assert len(list((world["folder"] / "precombo").glob("SC-*.json"))) == 2
|
||||
|
||||
@@ -212,5 +214,5 @@ def test_자료집_길_같은_이름_409_덮어쓰기(world, monkeypatch):
|
||||
over = client.post(url, json={**body, "overwrite": first.json()["키"]})
|
||||
assert over.status_code == 200 and over.json()["키"] == first.json()["키"]
|
||||
saved = world["folder"] / "precombo" / f"{first.json()['키']}.json"
|
||||
assert json.loads(saved.read_text(encoding="utf-8"))["담은로직"][0]["수량"] == {"손": 4}
|
||||
assert json.loads(saved.read_text(encoding="utf-8"))["담은로직"][0]["수량"] is None # 723-1
|
||||
assert client.post(url, json={**body, "cover": "S:없음"}).status_code == 404
|
||||
|
||||
@@ -93,15 +93,24 @@ def test_B09_서고_사본_값_같음_원본판(world, tmp_path):
|
||||
"precombo",
|
||||
{"이름": "조합", "단위": "㎡", "담은로직": [{"참조": ref["ref"], "수량": {"손": 2}}]},
|
||||
)["이름"]
|
||||
_, live = _top({"folder": world["personal"], "ref": {**MINE, "키": sc}}, 1)
|
||||
# 723-1 = SC 안 줄 수량(손 2) 저장 안 함 → 쓰는 쪽 알약 값으로 이음(층 · 사본 같은 값)
|
||||
pill = f"{refs.ref_id({**MINE, '키': sc})}#0"
|
||||
with store.at(world["personal"]):
|
||||
top = labor.clean(
|
||||
{"열": "rv", "담은로직": [{"참조": {**MINE, "키": sc}, "수량": {"손": 1}}]}
|
||||
)
|
||||
live = labor.calc(top, {pill: 2})["줄"][0]
|
||||
area = {"시트": [{"id": "s", "칸": {"A1": {"값": 2}}, "연결": [{"칸": "A1", "키": pill}]}]}
|
||||
before = _print(world["personal"])
|
||||
root = tmp_path / "proj"
|
||||
made = library.import_copy(root, "precombo", "personal", "7/42", sc)
|
||||
assert made["만듦"] and _print(world["personal"]) == before
|
||||
folder = library.library_dir(root) / made["폴더"]
|
||||
read = {p.name: json.loads(p.read_text(encoding="utf-8")) for p in folder.glob("*.json")}
|
||||
got = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, None)["줄"][0]
|
||||
assert not got["까닭"]
|
||||
bare = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, None)["줄"][0]
|
||||
assert bare["기준"] is None and "수량 없음" in bare["까닭"] # 이은 값 없음 = 1 로 안 셈
|
||||
got = prices.calc(read[source.LABOR_FILE], read[source.PRICE_FILE], {}, {}, area)["줄"][0]
|
||||
assert not got["까닭"] and not live["까닭"]
|
||||
assert got["기준"]["계"] == pytest.approx(live["기준"]["계"], rel=1e-9)
|
||||
mark = read["manifest.json"]["원본판"]
|
||||
built = library._build("precombo", "personal", "7/42", sc)[0]
|
||||
|
||||
@@ -21,7 +21,7 @@ from B09_Estimation import B09_Estimation_Refresh as refresh
|
||||
from B09_Estimation import B09_Estimation_Sangeun as sangeun
|
||||
from B09_Estimation.B09_Estimation_Calc_Base import compute_base
|
||||
from B09_Estimation.B09_Estimation_Calc_Labor import compute_labor
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import ASK, daega_inputs
|
||||
from B09_Estimation.B09_Estimation_Linked import linked_resource_keys
|
||||
from M02_MasterTemplete import M02_Item_Copy as item_copy
|
||||
from M02_MasterTemplete import M02_Item_Snapshot as snapshots
|
||||
@@ -122,15 +122,46 @@ def test_SK_단가_덮개_속_일위대가_펼침(world, tmp_path):
|
||||
if not _engine_ready(world):
|
||||
pytest.skip("715-1b 엔진 입력 덧칸(단가 · 값묶음) 뒤 — 지금은 M02 SK 셈도 못 함")
|
||||
key = _mine(world, full=True)
|
||||
live = _calc(world["personal"], key)
|
||||
# 723-1 = 속 일위대가 안 줄 수량 저장 안 함 → M02 SK = 수량 없음 · B09 = 그 줄만 빈 줄(이 공사 입력)
|
||||
assert "수량 없음" in _calc(world["personal"], key)["까닭"]
|
||||
root, folder = _lib(world, tmp_path, key)
|
||||
daega, done, _ = _b09(root)
|
||||
block = done[daega["libs"][folder]]
|
||||
lines = block["lines"]
|
||||
assert lines[1]["kind"] == "fixed" and lines[1]["prices"]["labor"] == pytest.approx(200000)
|
||||
sc_line = next(ln for ln in lines if ln["kind"] == "daega")
|
||||
assert done[sc_line["key"]]["kind"] == "사전조합" and sc_line["qty"] == 2
|
||||
_same_money(block, live)
|
||||
sc = done[sc_line["key"]]
|
||||
assert sc["kind"] == "사전조합" and sc_line["qty"] == 2
|
||||
assert sc["blank"] == 1 and sc["lines"][0]["why"] == ASK and block["blank"] >= 1
|
||||
# 이 공사에서 입력(702 덮개 qty 1) = 옛 손 1 과 같은 값 → M02 셈(알약 값 1) 과 같은 금액
|
||||
cover = {sc["cover"]: {sc["lines"][0]["cid"]: {"qty": 1}}}
|
||||
block = _b09(root, cover)[1][daega["libs"][folder]]
|
||||
assert block["blank"] == 0
|
||||
sc_ref = next(x for x in _added(world, key) if "참조" in x)["참조"]
|
||||
_same_money(block, _calc_given(world["personal"], key, {f"{refs.ref_id(sc_ref)}#0": 1}))
|
||||
|
||||
|
||||
def _added(world, key):
|
||||
with store.at(world["personal"]):
|
||||
return store.read("sangeun", key)["문서"]["더한줄"]
|
||||
|
||||
|
||||
def _calc_given(folder, key, pills):
|
||||
"""M02 SK 셈 — 속 일위대가 안 줄 수량 = 알약 값(723-1 `Expand` given)."""
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||||
from M02_MasterTemplete.M02_Labor_Expand import Expand
|
||||
|
||||
whole = m01.loaded()[1]
|
||||
with store.at(folder):
|
||||
clean = sd.clean(store.read("sangeun", key)["문서"], None, whole)
|
||||
me = refs.ref_id({"층": refs.here()[0], "주인": refs.here()[1], "키": clean["키"]})
|
||||
got = calc.run(
|
||||
clean, whole, None, refs.read_ref, Expand(whole, refs.read_ref, pills), (me,)
|
||||
)
|
||||
assert not got.get("까닭")
|
||||
return got
|
||||
|
||||
|
||||
def test_구조물_사본_SK_줄_업데이트_덮개_입력값_고른자재_굳힌키_그대로(
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
- 723-1 = M02 일위대가(SC) 안 줄 · 자재 수량 저장 안 함(null = 수량 없음 · 1 아님)
|
||||
- 구조물 조합에서 온 SC = 그 사본 면적 알약 값(「참조 id#차례」 · 「참조 id#자재|코드」)으로 굳힘
|
||||
- 내역에서 바로 쓴 SC(서고 사본) = 빈 줄 `ask` → 이 공사에서 입력(702 덮개 qty) → 금액
|
||||
- 옛 손(B09 굳힌 사본) = 그대로 · 덮개 열쇠 `C:참조` 그대로(기존 공사 금액 · 덮개 같음)
|
||||
- 옛 손(B09 굳힌 사본) = 그대로 · 덮개 열쇠 `C:참조` 그대로(기존 공사 금액 · 덮개 같음) ·
|
||||
알약 값 쓴 블록만 `C:참조|S:사본`(사본마다 값 다름 · 이 공사 입력 덮개와 안 섞임)
|
||||
- 셈 짝(`B09_Estimation_Calc_Labor` .py ↔ .ts) 거울
|
||||
"""
|
||||
|
||||
@@ -59,7 +60,7 @@ def test_수량_없는_SC_서고_사본_경고():
|
||||
daega, block = _build(_doc(), ({}, "L:lib"))
|
||||
assert [ln["qty"] for ln in block["lines"]] == [None, None, None]
|
||||
assert all(ln["ask"] == ASK for ln in block["lines"])
|
||||
assert block["cover"] == f"C:{RID}|L:lib" # 다른 곳 SC 덮개와 안 섞임
|
||||
assert block["cover"] == f"C:{RID}" # 공사 안 한 덮개(703-1 지난 공사 가져옴 꼴 그대로)
|
||||
out = compute_labor(daega, SHEETS)
|
||||
got = out["daega_blocks"][0]
|
||||
assert got["blank"] == 3 and got["sum"] == 0
|
||||
@@ -103,3 +104,53 @@ def test_거울_파이썬_TS():
|
||||
py = json.loads(json.dumps(compute_labor(daega, SHEETS, {}, cover), ensure_ascii=False))
|
||||
ts = b145._ts(daega, SHEETS, {}, cover)
|
||||
assert b145._same(py, ts["out"])
|
||||
|
||||
|
||||
def _structure(tmp_path, area):
|
||||
"""구조물 사본(참조 SC 한 줄 · 굳힌 SC = 수량 없음) + 면적.json(SC 안 줄 알약 연결 · 723-1)."""
|
||||
from M02_MasterTemplete import M02_Item_Snapshot as snapshots
|
||||
|
||||
root = b145._project(tmp_path / "proj", [(100, "SS000901")])
|
||||
sc = {**_doc(), "자재": []}
|
||||
b145._snap(root, "pipe@100.0", "SS000901", [{"참조": REF, "수량": {"손": 1}}], {}, {RID: sc})
|
||||
folder = snapshots.structures_dir(root) / snapshots.folder_name("pipe@100.0")
|
||||
if area is not None:
|
||||
(folder / "면적.json").write_text(json.dumps(area, ensure_ascii=False), encoding="utf-8")
|
||||
return root, folder
|
||||
|
||||
|
||||
def _sc_block(root):
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs
|
||||
|
||||
got = daega_inputs(root)
|
||||
combo = next(b for b in got["blocks"] if b["id"] == got["owners"]["pipe@100.0"])
|
||||
return got, next(b for b in got["blocks"] if b["id"] == combo["lines"][0]["key"])
|
||||
|
||||
|
||||
def test_구조물_사본_면적_알약_값_굳힘_설계_연동(tmp_path):
|
||||
links = [{"칸": "A1", "키": f"{RID}#0"}, {"칸": "A2", "키": f"{RID}#1"}]
|
||||
area = {"시트": [{"id": "s", "칸": {"A1": {"값": 1.5}, "A2": {"값": 2}}, "연결": links}]}
|
||||
root, folder = _structure(tmp_path, area)
|
||||
got, block = _sc_block(root)
|
||||
assert [ln["qty"] for ln in block["lines"]] == [1.5, 2.0]
|
||||
assert block["cover"] == f"C:{RID}|S:{folder.name}" and not any(
|
||||
"ask" in ln for ln in block["lines"]
|
||||
)
|
||||
out = compute_labor(got, SHEETS)
|
||||
by_id = {b["id"]: b for b in out["daega_blocks"]}
|
||||
assert by_id[block["id"]]["sum"] == 1500.0 + 4000.0
|
||||
assert by_id[got["owners"]["pipe@100.0"]]["sum"] == 5500.0 # 조합 = SC 1 단위 × 1
|
||||
# 설계 연동 — 면적 값이 바뀌면 다시 셈(사본 면적.json)
|
||||
area["시트"][0]["칸"]["A1"]["값"] = 3
|
||||
(folder / "면적.json").write_text(json.dumps(area, ensure_ascii=False), encoding="utf-8")
|
||||
assert [ln["qty"] for ln in _sc_block(root)[1]["lines"]] == [3.0, 2.0]
|
||||
|
||||
|
||||
def test_구조물_사본_알약_안_이으면_빈_줄(tmp_path):
|
||||
root, folder = _structure(tmp_path, None)
|
||||
got, block = _sc_block(root)
|
||||
assert [ln["qty"] for ln in block["lines"]] == [None, None]
|
||||
assert block["cover"] == f"C:{RID}" # 알약 안 씀 = 서고 SC 와 같은 「이 공사 입력」 덮개
|
||||
out = compute_labor(got, SHEETS)
|
||||
owner = next(r for r in out["daega"] if r["key"] == got["owners"]["pipe@100.0"])
|
||||
assert owner["blank"] >= 1 # 부른 SC 빈 줄 = 조합도 빈 줄(166-6)
|
||||
|
||||
Reference in New Issue
Block a user