Files
Aislo/resources/tester/test_840_2_machine_sheet.py
T
eomsangdonandClaude Opus 5.5 f822a4c36a feat(840-2): 기계 보기 공용 — M01 기계 상세 · 내 기계를 왼쪽 제어 구역 + 오른쪽 시간당 경비 산출근거로 · 「복사해 내 기계로」 걷음 · 기계 줄 풀기 안 내놓음
- 기계 보기 부품(A00_Common/line_stack_machine_sheet) — 836-1 제어 구역 그대로(기계 표 꼴 훅 sheet · 잠금 lock) + 기본 정보 · 손료 근거 접이 카드 + 비슷한 기계에서 시작 · 값 바꾸면 브라우저 계산기로 즉시
- 서버 POST /api/m02/machine-sheet(M02_MyMachine_Sheet) — 식 고름 + 식 문서 + 처음 줄 값 + 연결 기록 · 쓰기 없음 · 파이썬 bind ↔ TS bindDoc 짝
- 덮음 ↔ 기계 줄(common_util_machine_sheet) — 고침 없음 = 줄 바이트 같음 · 내 기계 바로 · 직접 자리 맞춤
- 내 기계 = M01 에 없는 기계만 — 화면 만들기 · 고치기에서 M01 과 같은 이름 · 규격 409(불러오기 자동 생성은 그대로) · [새 기계] · 고침 열림
- 옛 기계 양식 부품(ui_template_machine_form) 지움 — 쓰는 곳 0(부품 모음 견본 줄도 걷음)
- 낡은 시험 test_821_3 test_stack_line_copy — 836-2 자동 따라옴(내 기계 고치면 SK 금액 따라감 · 갱신 할 것 없음)에 맞춤
- 지킴 시험 test_840_2 — M01 기계 655 대 박은 식 셈 = 요소 셈 · 줄 그대로 · 굴착기 덮음 · 내 기계 꼴 · M01 같은 이름 막음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-15 15:52:13 +09:00

201 lines
9.4 KiB
Python

"""840-2 기계 보기 지킴 시험 — 값 박음 · 덮음 ↔ 기계 줄 · M01 같은 이름 막음 · 쓰기 없음.
짝 = `M02_MasterTemplete/M02_MyMachine_Sheet.py` `bind` ↔ `common_util/common_util_machine_sheet.ts` `bindDoc`
(+ 덮음 ↔ 줄 `coverFrom` · `applyCover` — 화면 몫 · Node 로 돌림).
① M01 기계 655 대 전수 — 식 고름 → 값 박은 식 셈 = 기계 표 요소로 읽는 셈(노 · 재 · 경 · 계 같음) · 식 없음 = 까닭.
② 655 대 전수(TS) — 고침 없음 = 덮음 없음 · 줄 JSON 같음(열고 닫아도 M01 기계 파일 그대로) · 박음 거울.
③ 굴착기 1.0㎥ — 가격 · 조종원(2 인 · 직종 · 뺌) · 잡품 율 덮음 → 줄 칸 · 덮음 얹고 박은 셈 = 줄 고친 셈.
④ 내 기계 꼴 — 바로(상각비계수 → 손료계수) · 직접(시간당) · 처음 값으로 되돌리면 줄 같음.
⑤ M01 과 같은 이름 · 규격 = 409 · 다른 이름 = 통과(검사만 · 쓰기 없음).
"""
import copy
import importlib
import json
import os
import subprocess
from decimal import Decimal as D
import pytest
from M01_MasterData import M01_MasterData_Store as m01 # scripts 길을 둠
from M01_MasterData import M01_MasterData_Doc as m01doc
from M02_MasterTemplete import M02_Machine_Hourly as mh
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_MyMachine as mym
from M02_MasterTemplete import M02_MyMachine_Sheet as ms
mc = importlib.import_module("master_doc_calc")
ml = importlib.import_module("master_doc_link")
mcv = importlib.import_module("master_doc_cover")
HERE = os.path.dirname(os.path.abspath(__file__))
COSTS = ("노무비", "재료비", "경비", "계")
DIG = "EQ000022" # 굴착기(무한궤도) 1.0㎥ 0201-0100 · GC000267
def _plain(v):
return json.loads(json.dumps(v, ensure_ascii=False, default=ms._plain))
@pytest.fixture(scope="module")
def world():
"""M01 기계 줄 · 식 고름 · 식 문서 — 한 번만(마스터 층 · 쓰기 없음)."""
with store.at(store.FOLDER):
rows = m01.loaded()[0]["기계.json"]["줄"]
docs = m01doc.docs()
links = ml.Links(m01.loaded()[1], docs)
picks = {}
for r in rows:
got = mh.pick({**r, "값": m01._dec(r.get("값") or {})})
picks[r["키"]] = str(got["키"]) if got and str(got["키"]) in docs else None
yield {"rows": rows, "docs": docs, "links": links, "picks": picks}
def _node(payload):
proc = subprocess.run(
["node", os.path.join(HERE, "helper_840_2_machine_sheet.cjs")],
input=json.dumps(_plain(payload), ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
return json.loads(proc.stdout)
def _money(a):
return {k: a.get(k) for k in COSTS}
# ── ① 655 대 금액 ──────────────────────────────────────────────────────
def test_655대_박은_식_셈_같음(world):
links, docs = world["links"], world["docs"]
same = none = 0
for r in world["rows"]:
key = world["picks"][r["키"]]
if key is None:
none += 1
continue
given = {"기계": str(r.get("원문번호"))}
before = mc.calc(docs[key], links, given)
after = mc.calc(ms.bind(docs[key], m01._dec(r.get("값") or {})), links, given)
assert _money(after) == _money(before), (r["키"], key)
same += before["셈됨"]
assert len(world["rows"]) == 655 and same >= 600 and none <= 44
def test_식_없는_기계_까닭(world):
with store.at(store.FOLDER):
for r in world["rows"]:
if world["picks"][r["키"]] is None:
got = ms.sheet(_plain(r))
assert got["키"] is None and got["문서"] is None and got["까닭"]
return
pytest.skip("식 없는 기계 없음")
# ── ② 655 대 덮음 ↔ 줄(TS) ──────────────────────────────────────────────
def test_655대_고침_없음_줄_같음_박음_거울(world):
docs, targets, want = {}, [], []
for r in world["rows"]:
key = world["picks"][r["키"]]
if key is None:
continue
docs[key] = docs.get(key) or _plain(world["docs"][key])
bind = r["키"] in (DIG, "EQ000001", world["rows"][-1]["키"])
targets.append({"식": key, "처음": _plain(r), "개인": False,
**({"박음": _plain(r["값"])} if bind else {})}) # fmt: skip
want.append(_plain(ms.bind(world["docs"][key], r["값"])) if bind else None)
got = _node({"문서들": docs, "대상": targets})
for t, g, w in zip(targets, got, want):
assert g["덮음"] == {}, t["처음"]["키"]
assert json.dumps(g["줄"], ensure_ascii=False) == json.dumps(t["처음"], ensure_ascii=False)
if w is not None:
assert g["박음"] == w
# ── ③ 굴착기 덮음 → 줄 · 셈 ────────────────────────────────────────────
def test_굴착기_덮음_줄_칸_셈(world):
row = _plain(next(r for r in world["rows"] if r["키"] == DIG))
doc = world["docs"]["GC000267"]
given = {"기계": row["원문번호"]}
jobs = ms._jobs()
lb = next(k for k, v in jobs.items() if v == "1002")
cases = [
({"값": {"가격": 150000}}, {"가격": 150000}),
({"조종원": [{"키": "LB:1048", "인": 2}]}, {"조종원": 2}),
({"조종원": [{"키": lb, "인": 1}]}, {"조종원직종": "1002"}),
({"조종원": []}, {"조종원": 0}),
({"잡품": [{"줄": "주연료", "율": 30}]}, {"잡재료율": 30}),
]
targets = [
{"식": "GC000267", "처음": row, "덮음": c, "개인": False, "직종": jobs} for c, _ in cases
]
got = _node({"문서들": {"GC000267": _plain(doc)}, "대상": targets})
for (cover, change), g in zip(cases, got):
want = copy.deepcopy(row)
want["값"].update(change)
assert g["줄"] == want, cover
shaped = ms.bind(
mcv.cover_doc(doc, given, {row["원문번호"]: m01._dec(cover)}), m01._dec(row["값"])
)
edited = ms.bind(doc, m01._dec(g["줄"]["값"]))
a, b = mc.calc(shaped, world["links"], given), mc.calc(edited, world["links"], given)
assert a["셈됨"] and _money(a) == _money(b), cover
# 지금 줄(초안) → 덮음 — 처음 줄 대비 고친 칸만
cur = copy.deepcopy(row)
cur["값"].update({"가격": 150000, "조종원": 2, "잡재료율": 30})
made = _node({"문서들": {"GC000267": _plain(doc)},
"대상": [{"식": "GC000267", "처음": row, "지금": cur, "개인": False}]})[0] # fmt: skip
assert made["덮음"] == {
"값": {"가격": 150000},
"조종원": [{"키": "LB:1048", "인": 2}],
"잡품": [{"줄": "주연료", "율": 30}],
}
assert made["줄"] == cur
# ── ④ 내 기계 꼴 ───────────────────────────────────────────────────────
def test_내기계_바로_직접(world):
easy = {"이름": "t", "규격": "", "방식": "바로",
"값": {"가격": 1000, "손료계수": 2000, "주연료": 5, "주연료종류": "경유"}} # fmt: skip
hour = {"이름": "t", "규격": "", "방식": "직접", "값": {},
"시간당": {"노무비": 1, "재료비": 2, "경비": 3}} # fmt: skip
with store.at(store.FOLDER):
a, b = ms.sheet(easy, mine=True), ms.sheet(hour, mine=True)
assert a["키"] == "GC000272" and b["키"] == "GC001199"
assert a["값"]["상각비계수"] == 2000 and a["값"]["정비비계수"] == 0
docs = {a["키"]: a["문서"], b["키"]: b["문서"]}
targets = [
{"식": a["키"], "처음": easy, "개인": True},
{"식": a["키"], "처음": easy, "개인": True, "덮음": {"값": {"상각비계수": 2500}}},
{"식": b["키"], "처음": hour, "개인": True},
{"식": b["키"], "처음": hour, "개인": True, "덮음": {"값": {"경비": 10}}},
]
got = _node({"문서들": docs, "대상": targets})
assert got[0]["덮음"] == {} and got[0]["줄"] == easy
assert got[1]["줄"]["값"] == {**easy["값"], "손료계수": 2500}
assert got[2]["덮음"] == {} and got[2]["줄"] == hour
assert got[3]["줄"]["시간당"] == {"노무비": 1, "재료비": 2, "경비": 10}
# ── ⑤ M01 같은 이름 막음 ───────────────────────────────────────────────
def test_M01_같은_이름_막음(world):
dig = next(r for r in world["rows"] if r["키"] == DIG)
with store.at(store.FOLDER):
with pytest.raises(store.StoreError) as e:
mym.not_m01({"이름": dig["이름"], "규격": dig["규격"]})
assert e.value.status == 409
mym.not_m01({"이름": "sub3임시 없는 기계", "규격": "1"})
def test_서버_덮음_몸_틀림_400():
with store.at(store.FOLDER), pytest.raises(m01.StoreError) as e:
ms.sheet({"원문번호": "0201-0100", "값": {}}, None, {"값": {"가격": -1}})
assert e.value.status == 400
def test_수_꼴():
assert ms._plain(D("1.50")) == 1.5 and ms._plain(D("3")) == 3