feat(727-2 · 727-3): 둘째 기초 세부 줄 펼침 · 세부 덮개 · 뺀줄 · 출처 꼬리표 · 넣은 뒤 고른 옵션 SK 입력값 저장
- 서버 Sangeun_Doc: 더한줄 로직 = 덮개{세부 cid} · 뺀줄[세부 cid] 받음 · SK 입력값 = 바탕 입력 + 더한줄 기초 입력(727-3)
- 서버 Sangeun_Calc: 고친 둘째 기초 = 고친 기초 줄을 엔진으로 셈한 값묶음 줄(B09 굳힘도 같은 값) · 화면 셈 길만 펼침(세부 × 수량) · 뺀세부 · 바탕판 · 꼬리표(원문번호) · 저장 때 세부 원값 · 바탕판
- 화면 Cover_Model: 세부 줄 열쇠 add:#k/세부 cid · 품 = 화면 품 ÷ 수량 · 줄 빼기 · 되살림 · 기초 빼기 · 꼬리표 = 기초 둘 이상일 때만
- 화면 Sangeun_Detail: 옵션 칸 = 바탕 입력 + 더한줄 기초 입력 · 고르면 SK 입력값(열린입력만 시험 값) · M01 개정 알림에 둘째 기초 · [기초 빼기]
- 공용 상세 LogicLab_Ho: 줄 덧칸 꼬리표 배지(칸 폭 고정 · 말줄임 + 풍선 · M01 화면은 꼬리표 없음 = 전후 같음)
- 이음: 둘째 기초 수량 = 그 기초 기준단위 몇 개(처음 1) — 단위 · 배는 728
- 새 시험 test_727_sk_base_add(세부 둘 0.023 · 0.01546 · 기초 둘 계 = 서버 셈 · 덮개 · 뺀줄 · 굳힘 같은 값 · 암석작업 옵션 5,346.93) · 기존 SK 넷 금액 전후 같음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"""727 M02 단가산출 기초 넣기(요구서_내역19 · 내역 20-1) — 임시 storage · 실제 M01 읽기만.
|
||||
|
||||
- 727-1 고르기 창 기초 = 로직마다 한 줄(`logic/M02_Find_Heads.ts` · 옛 창도) · 규격 칸 = 「옵션 N」
|
||||
- 727-2 첫 기초 = 바탕 · 둘째부터 = 더한줄 세부 펼침(덮개 · 뺀줄 · M01 개정 지문 · 꼬리표) · B09 굳힘 같은 값
|
||||
- 727-2 첫 기초 = 바탕 · 둘째부터 = 더한줄 세부 펼침(덮개 · 뺀줄 · M01 개정 지문 · 꼬리표) ·
|
||||
B09 굳힘 같은 값
|
||||
- 727-3 넣은 뒤 고른 옵션 = SK `입력값` → 일위대가(펼침) · 굳힘도 그 옵션
|
||||
"""
|
||||
|
||||
@@ -12,8 +13,17 @@ import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Item_Prices as prices
|
||||
from M02_MasterTemplete import M02_Item_Source as source
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Logic_Standard as standard
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
from resources.tester.test_m02_labor_ref import MINE, world # noqa: F401 — 같은 임시 층
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
M02 = ROOT / "M02_MasterTemplete"
|
||||
@@ -65,3 +75,163 @@ def test_727_1_두_창_모두_기초_한_줄():
|
||||
assert "std = s?.줄.length ? logicHeads(s.줄) : null;" in find
|
||||
assert "std = got.줄.length ? logicHeads(got.줄) : null;" in old
|
||||
assert "logicOnly" not in find
|
||||
|
||||
|
||||
# ── 727-2 · 727-3 서버(임시 storage) ───────────────────────────────────────
|
||||
# 금속기와 해체 3-2-1 · 원/㎡ · 지붕잇기공 0.018 · 보통인부 0.012 · 덧줄 공구손료 3 %
|
||||
PCT = "GC001109"
|
||||
SECOND = {"종류": "로직", "로직": PCT, "입력값": {}, "수량": 1}
|
||||
COVER = {"labor:LB000036#0": {"qty": 0.036}}
|
||||
DROP = ["pct:공구손료 및 경장비#0"]
|
||||
|
||||
|
||||
def _sk(folder, **doc):
|
||||
with store.at(folder):
|
||||
made = refs.create("sangeun", {"이름": "sub7임시", "단위": "㎥", "더한줄": [], **doc})
|
||||
return store.read("sangeun", made["이름"])["문서"]
|
||||
|
||||
|
||||
def _run(folder, doc, given=None):
|
||||
with store.at(folder):
|
||||
return calc.calc_doc(doc, given)
|
||||
|
||||
|
||||
def _line(got, cid):
|
||||
return next(x for x in got["줄"] if x["cid"] == cid)
|
||||
|
||||
|
||||
def _m01(code, values=None):
|
||||
return float(labor.run_line(m01.loaded()[1], code, values or {}, None, None)["기준"]["계"])
|
||||
|
||||
|
||||
def test_727_2_첫_기초_바탕_세부_줄(world): # noqa: F811
|
||||
doc = _sk(world["personal"], 바탕={"로직": GF})
|
||||
got = _run(world["personal"], doc)
|
||||
assert _line(got, "labor:LB000002#0")["수량"] == pytest.approx(0.023)
|
||||
assert _line(got, "logic:GC000267#0")["수량"] == pytest.approx(0.01546, abs=1e-5)
|
||||
assert got["꼬리표"] == "9-12-1"
|
||||
|
||||
|
||||
def test_727_2_기초_둘_세부_펼침_계(world): # noqa: F811
|
||||
doc = _sk(world["personal"], 바탕={"로직": GF}, 더한줄=[SECOND])
|
||||
bare = _run(world["personal"], _sk(world["personal"], 바탕={"로직": GF}))
|
||||
got = _run(world["personal"], doc)
|
||||
add = _line(got, "add:#0")
|
||||
assert add["꼬리표"] == "3-2-1" and add["바탕판"] and add["뺀세부"] == []
|
||||
sub = {x["cid"]: x for x in add["펼침"]}
|
||||
assert list(sub) == [
|
||||
"add:#0/labor:LB000036#0",
|
||||
"add:#0/labor:LB000002#0",
|
||||
"add:#0/pct:공구손료 및 경장비#0",
|
||||
]
|
||||
assert sub["add:#0/labor:LB000036#0"]["수량"] == pytest.approx(0.018)
|
||||
assert sum(x["금액"] for x in add["펼침"]) == pytest.approx(add["금액"], rel=1e-9)
|
||||
assert add["금액"] == pytest.approx(_m01(PCT), rel=1e-9)
|
||||
assert got["기준"]["계"] == pytest.approx(bare["기준"]["계"] + _m01(PCT), rel=1e-9)
|
||||
with store.at(world["personal"]): # 펼침 없는 셈(쓰는 자리) = 같은 계
|
||||
plain = calc.run(doc, m01.loaded()[1])
|
||||
assert float(plain["기준"]["계"]) == pytest.approx(got["기준"]["계"], rel=1e-9)
|
||||
|
||||
|
||||
def test_727_2_둘째_기초_덮개_뺀줄_저장(world): # noqa: F811
|
||||
one = {**SECOND, "수량": 2, "덮개": COVER, "뺀줄": DROP}
|
||||
doc = _sk(world["personal"], 바탕={"로직": GF}, 더한줄=[one])
|
||||
saved = doc["더한줄"][0]
|
||||
assert saved["덮개"]["labor:LB000036#0"] == {
|
||||
"qty": 0.036,
|
||||
"원값": {"qty": 0.018, "단가": 233250},
|
||||
}
|
||||
assert saved["뺀줄"] == DROP and saved["바탕판"]
|
||||
got = _run(world["personal"], doc)
|
||||
add = _line(got, "add:#0")
|
||||
want = 2 * (0.036 * 233250 + 0.012 * 172068)
|
||||
assert add["금액"] == pytest.approx(want, rel=1e-9)
|
||||
sub = {x["cid"]: x for x in add["펼침"]}
|
||||
assert sub["add:#0/labor:LB000036#0"]["덮개"] == ["qty"]
|
||||
assert sub["add:#0/labor:LB000036#0"]["수량"] == pytest.approx(0.072)
|
||||
assert add["뺀세부"] == [{"cid": "add:#0/" + DROP[0], "이름": "공구손료 및 경장비"}]
|
||||
lost = {**one, "덮개": {"labor:LB999999#0": {"qty": 1}}}
|
||||
got = _run(world["personal"], {**doc, "더한줄": [lost]})
|
||||
assert any(x["cid"] == "add:#0/labor:LB999999#0" for x in got["짝잃음"])
|
||||
|
||||
|
||||
def test_727_2_굳힘_같은_값(world, monkeypatch): # noqa: F811
|
||||
one = {**SECOND, "덮개": COVER, "뺀줄": DROP}
|
||||
key = _sk(
|
||||
world["personal"],
|
||||
바탕={"로직": GF},
|
||||
더한줄=[one],
|
||||
입력값={"보정작업": "암석작업(연암・보통암・경암)"},
|
||||
)
|
||||
ref = {**MINE, "키": key["키"]}
|
||||
with store.at(world["personal"]):
|
||||
top = labor.clean({"열": "rv", "담은로직": [{"참조": ref, "수량": {"손": 2}}]})
|
||||
live = labor.calc(top, {})["줄"][0]
|
||||
rows = source.labor_doc_rows(top)
|
||||
bundle = source.price_bundle(rows, world["personal"])
|
||||
frozen = rows["참조사본"][refs.ref_id(ref)]
|
||||
assert frozen["굳힌줄"]["호표"][-1]["값묶음"] # 고친 둘째 기초 = 셈한 값묶음 줄
|
||||
got = prices.calc(rows, json.loads(json.dumps(bundle, ensure_ascii=False)), {}, {}, None)
|
||||
line = got["줄"][0]
|
||||
assert not line["까닭"] and not live["까닭"]
|
||||
for part in ("노무비", "재료비", "경비", "계"):
|
||||
assert line["기준"][part] == pytest.approx(live["기준"][part], rel=1e-9)
|
||||
|
||||
|
||||
def test_727_3_옵션_저장_일위대가_그_옵션(world): # noqa: F811
|
||||
rock = {"지역": "전국평균", "보정작업": "암석작업(연암・보통암・경암)"}
|
||||
doc = _sk(
|
||||
world["personal"], 바탕={"로직": GF}, 입력값={"보정작업": "암석작업(연암・보통암・경암)"}
|
||||
)
|
||||
assert doc["입력값"] == {"보정작업": "암석작업(연암・보통암・경암)"} # 다시 열면 그대로
|
||||
with store.at(world["personal"]):
|
||||
up = refs.Expand(m01.loaded()[1])({**MINE, "키": doc["키"]})
|
||||
assert float(up["기준"]["계"]) == pytest.approx(_m01(GF, rock), rel=1e-9)
|
||||
assert _m01(GF, rock) > _m01(GF, {"지역": "전국평균", "보정작업": "해당 없음"})
|
||||
# 둘째 기초 입력(바탕에 없는 이름)도 SK 입력값에 남고 그 옵션으로 셈
|
||||
two = _sk(world["personal"], 바탕={"로직": PCT}, 더한줄=[{**SECOND, "로직": GF}], 입력값=rock)
|
||||
assert two["입력값"] == rock
|
||||
got = _run(world["personal"], two)
|
||||
assert _line(got, "add:#0")["금액"] == pytest.approx(_m01(GF, rock), rel=1e-9)
|
||||
|
||||
|
||||
def _model(ans, doc, script):
|
||||
body = (
|
||||
f"\nconst ans = {json.dumps(ans, ensure_ascii=False)};"
|
||||
f"\nconst doc = {json.dumps(doc, ensure_ascii=False)};\n{script}"
|
||||
)
|
||||
return _node(["M02_MasterTemplete/logic/M02_Sangeun_Cover_Model.ts"], body)
|
||||
|
||||
|
||||
def test_727_2_화면_꼴_세부_줄_고침(world): # noqa: F811
|
||||
doc = _sk(world["personal"], 바탕={"로직": GF}, 더한줄=[{**SECOND, "수량": 2}])
|
||||
ans = _run(world["personal"], doc)
|
||||
got = _model(
|
||||
ans,
|
||||
doc,
|
||||
"""
|
||||
const rows = rowsOf(ans, null, doc);
|
||||
const view = hoView(ans, null, null, doc);
|
||||
const sub = rows.find((r) => r.cid === "add:#0/labor:LB000036#0");
|
||||
setLineCover(doc, sub, { qty: 0.072 }, false);
|
||||
const covered = JSON.parse(JSON.stringify(doc.더한줄[0]));
|
||||
dropLine(doc, rows.find((r) => r.cid === "add:#0/pct:공구손료 및 경장비#0"));
|
||||
const dropped = [...doc.더한줄[0].뺀줄];
|
||||
restoreLine(doc, "add:#0/pct:공구손료 및 경장비#0");
|
||||
const one = { ...doc, 더한줄: [] };
|
||||
console.log(JSON.stringify({
|
||||
cids: rows.map((r) => r.cid), boxes: rows.map((r) => r.묶음), sub, covered, dropped,
|
||||
back: doc.더한줄[0].뺀줄 ?? null, tags: view.호표.map((x) => x.꼬리표 ?? ""),
|
||||
extraTags: view.덧줄.map((x) => x.꼬리표 ?? ""), viewCids: view.cids,
|
||||
single: [...tagsOf(ans, one).values()],
|
||||
}));
|
||||
""",
|
||||
)
|
||||
assert "add:#0" not in got["cids"] and "add:#0/labor:LB000036#0" in got["cids"]
|
||||
assert got["sub"]["sub"] == {"k": 0, "cid": "labor:LB000036#0", "scale": 2}
|
||||
assert got["sub"]["수량"] == pytest.approx(0.036) and got["sub"]["묶음"] == "인력"
|
||||
assert got["covered"]["덮개"]["labor:LB000036#0"]["qty"] == pytest.approx(0.036) # ÷ 수량 2
|
||||
assert got["covered"]["덮개"]["labor:LB000036#0"]["원값"] == {"qty": 0.018, "단가": 233250}
|
||||
assert got["dropped"] == DROP and got["back"] is None
|
||||
assert got["tags"] == ["9-12-1", "9-12-1", "3-2-1", "3-2-1"] # 바탕 둘 · 둘째 기초 인력 둘
|
||||
assert got["extraTags"] == ["3-2-1"] and got["single"] == [] # 기초 하나면 꼬리표 없음
|
||||
|
||||
Reference in New Issue
Block a user