# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀 """827-1 단가산출 기계 창 초안 — 임시 storage 만(M01 정본 · 공유 storage 안 씀). ① 저장 한 번 = 초안 내 기계 만들기 + 줄 연결(표지 → 진짜 키) + 사본 박기 · 미리 셈 = 저장 뒤 셈 · 새 SK(수식 저장 전) ② 같은 초안 두 줄 = 내 기계 하나 ③ 저장 안 함(미리 셈만) = 내 기계 0 ④ 실패(판 다름 · 저장 중 오류) = 내 기계 · SK 그대로(만든 것 지움 · 고친 것 되돌림) ⑤ 내 기계 고침 초안 → 그 SK 는 새 값 · 그 기계 쓰는 다른 SK 「단가새값」 → [단가 갱신] ⑥ 회사 층 · 남의 층 거절 ⑦ 확인 길 = 쓰기 없음 · 셈 기록(화면 즉시 셈) · 틀린 몸 400 ⑧ M01 기계 목록 시간당 = 정본 시간당. """ from __future__ import annotations import json import pytest from fastapi import HTTPException from M02_MasterTemplete import M02_Doc_Router as doc_route from M02_MasterTemplete import M02_Doc_Sk_Path as path from M02_MasterTemplete import M02_Labor_Ref as refs 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_Draft as draft from M02_MasterTemplete import M02_MyMachine_Router as route from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층 DIG = "EQ000022" # 굴착기(무한궤도) 1.0㎥ — 연료 · 조종원 있음 NAME = "sub1임시 기계" USER = {"role": "USER", "company_id": 7, "user_id": 42} def _at(folder, fn, *args): with store.at(folder): return fn(*args) def _body(price=1500): vals = {"가격": price, "손료계수": 2500, "주연료": 2, "주연료종류": "경유", "잡재료율": 10, "조종원": 1, "조종원직종": "1048"} # fmt: skip return {"이름": NAME, "규격": "1㎾", "방식": "바로", "값": vals} def _var(n, no, logic): return {"기호": "P", "아래": f"기계{n}", "위": "", "이름": NAME, "단위": "", "연결": {"갈래": "단가산출", "키": logic, "입력": {"기계": f"'{no}'"}}} # fmt: skip def _doc(key, nos, logic, drafts=None): """줄 쌓기 기계 줄(화면이 보내는 꼴) — 줄마다 기계='…' · 초안 칸.""" lines = [{"이름": NAME, "단위": "hr", "비목": "묶음", "수량": "1", "단가": f"P_기계{n}"} for n in range(len(nos))] # fmt: skip out = {"꼴": "수식문서", "판": 1, "키": key, "이름": "sub1임시", "결과단위": "원", "변수": [_var(n, no, logic) for n, no in enumerate(nos)], "식": [], "금액": lines} # fmt: skip return out | ({draft.FIELD: drafts} if drafts is not None else {}) def _sk(folder, name="sub1임시"): body = {"이름": name, "단위": "시간", "더한줄": []} return _at(folder, lambda: refs.create("sangeun", body)["이름"]) def _save(folder, key, doc): 판 = _at(folder, path.read, key)["판"] return _at(folder, path.save, key, {"문서": doc, "판": 판}) def _file(folder, key): return _at(folder, lambda: store.read("sangeun", key)["문서"]) def _new_draft(folder, tag="EQP초안-7", base=DIG, price=1500): got = _at(folder, draft.check, {"줄": _body(price), "본뜬": base}) item = {"표지": tag, "본뜬": base, "줄": _body(price)} return item, got["시간당"] def test_save_creates_links_and_stamps(world): """① 새 SK(수식 저장 전) — 미리 셈 = 확인 시간당 = 저장 뒤 셈 · 내 기계 1 · 표지 → 진짜 키 · 사본에 박힘.""" me = world["personal"] item, hour = _new_draft(me) assert hour["ok"] and hour["로직"] sk = _sk(me) doc = _doc(sk, ["개인 EQP초안-7"], hour["로직"], [item]) look = _at(me, path.preview, sk, {"문서": doc}) assert look["셈"]["계"] == hour["계"] assert _at(me, mym.rows) == [] # 미리 셈 = 쓰기 없음 after = _save(me, sk, doc) rows = _at(me, mym.rows) assert len(rows) == 1 and rows[0]["본뜬"] == DIG and rows[0]["값"]["가격"] == 1500 key = rows[0]["키"] saved = _file(me, sk)["수식"] text = json.dumps(saved["문서"], ensure_ascii=False) assert "EQP초안" not in text and draft.FIELD not in saved["문서"] assert f"'개인 {key}'" in text assert key in [r["키"] for r in saved["단가"]] # 사본에 박힘 assert after["셈"]["계"] == look["셈"]["계"] and after["사본"]["단가새값"] is False assert _at(me, mym.uses)[key] == [{"키": sk, "이름": "sub1임시"}] def test_same_draft_two_lines(world): """② 같은 초안을 두 줄이 고름 = 내 기계 하나 · 두 줄 모두 그 키.""" me = world["personal"] item, hour = _new_draft(me) sk = _sk(me) doc = _doc(sk, ["개인 EQP초안-7", "개인 EQP초안-7"], hour["로직"], [item]) look = _at(me, path.preview, sk, {"문서": doc}) _save(me, sk, doc) rows = _at(me, mym.rows) assert len(rows) == 1 text = json.dumps(_file(me, sk)["수식"]["문서"], ensure_ascii=False) assert text.count(f"'개인 {rows[0]['키']}'") == 2 assert _at(me, path.read, sk)["셈"]["계"] == look["셈"]["계"] == 2 * hour["계"] def test_unused_draft_not_made(world): """③ 문서가 안 쓰는 초안 = 안 만듦 · 미리 셈만 = 0.""" me = world["personal"] item, hour = _new_draft(me) sk = _sk(me) _at(me, path.preview, sk, {"문서": _doc(sk, ["개인 EQP초안-7"], hour["로직"], [item])}) assert _at(me, mym.rows) == [] _save(me, sk, _doc(sk, ["0101-0010"], "GC000269", [item])) # 줄을 다른 기계로 바꾼 뒤 저장 assert _at(me, mym.rows) == [] with pytest.raises(store.StoreError) as e: # 초안 없는 표지 _save(me, sk, _doc(sk, ["개인 EQP초안-9"], hour["로직"], [item])) assert e.value.status == 400 and _at(me, mym.rows) == [] def test_fail_leaves_nothing(world, monkeypatch): """④ 판 다름 409 · 저장 중 오류 = 만든 내 기계 지움 · 고친 내 기계 되돌림 · SK 그대로.""" me = world["personal"] item, hour = _new_draft(me) sk = _sk(me) doc = _doc(sk, ["개인 EQP초안-7"], hour["로직"], [item]) with pytest.raises(store.StoreError) as e: _at(me, path.save, sk, {"문서": doc, "판": "옛판"}) assert e.value.status == 409 and _at(me, mym.rows) == [] old = _at(me, mym.create, _body(1000))["줄"] edit = {"표지": "EQP초안-8", "키": old["키"], "줄": _body(4000)} both = _doc(sk, ["개인 EQP초안-7", "개인 EQP초안-8"], hour["로직"], [item, edit]) before = _file(me, sk) def boom(*_a, **_k): raise store.StoreError(500, "시험 실패") monkeypatch.setattr(store, "write", boom) with pytest.raises(store.StoreError): _save(me, sk, both) assert _at(me, mym.rows) == [old] # 만든 것 없음 · 고친 것 되돌림 assert _file(me, sk) == before def test_edit_mine_other_sk_new_value(world): """⑤ 내 기계 고침 초안 — 저장한 SK = 새 값 그대로 · 그 기계 쓰는 다른 SK = 단가새값 → [단가 갱신].""" me = world["personal"] made = _at(me, mym.create, _body(1000)) key, no, logic, was = (made["줄"]["키"], made["줄"]["원문번호"], made["시간당"]["로직"], made["시간당"]["계"]) # fmt: skip first = _sk(me, "sub1임시 첫째") _save(me, first, _doc(first, [no], logic)) assert _at(me, path.read, first)["셈"]["계"] == was second = _sk(me, "sub1임시 둘째") edit = {"표지": "EQP초안-3", "키": key, "줄": _body(5000)} doc = _doc(second, ["개인 EQP초안-3"], logic, [edit]) look = _at(me, path.preview, second, {"문서": doc}) assert look["셈"]["계"] > was assert _at(me, mym.rows)[0]["값"]["가격"] == 1000 # 미리 셈 = 원본 그대로 after = _save(me, second, doc) assert after["셈"]["계"] == look["셈"]["계"] and after["사본"]["단가새값"] is False assert _at(me, mym.rows)[0]["값"]["가격"] == 5000 and len(_at(me, mym.rows)) == 1 now = _at(me, path.read, first) assert now["셈"]["계"] == was and now["사본"]["단가새값"] is True job = _at(me, path.preview, first, {"할일": "단가갱신"}) assert [x["키"] for x in job["바뀐자원"]] == [key] and job["후"]["계"] == look["셈"]["계"] seen = _at(me, path.machine, first, f"'{no}'") assert ( seen["사본"]["값"]["가격"] == 1000 and seen["지금"]["값"]["가격"] == 5000 and seen["새값"] ) def test_company_and_other_refused(world): """⑥ 회사 층 = 404(내 기계는 개인 층만) · 남의 개인 층 = 403(일반 사용자).""" item, hour = _new_draft(world["personal"]) sk = _sk(world["company"]) doc = _doc(sk, ["개인 EQP초안-7"], hour["로직"], [item]) for fn in (path.preview, path.save): with pytest.raises(store.StoreError) as e: _at(world["company"], fn, sk, {"문서": doc, "판": ""}) assert e.value.status in (404, 409) with pytest.raises(store.StoreError) as e: _at(world["company"], path.preview, sk, {"문서": doc}) assert e.value.status == 404 with pytest.raises(HTTPException) as e: route.my_machine_draft("personal", route.MachineBody(줄=_body()), "7/43", USER) assert e.value.status_code == 403 with pytest.raises(HTTPException) as e: doc_route.save_doc("personal", sk, doc_route.DocBody(문서=doc, 판=""), "7/43", USER) assert e.value.status_code == 403 def test_check_no_write(world): """⑦ 확인 길 = 쓰기 없음 · 직접 = GC001199 · 빈 이름 400 · 없는 내 기계 404.""" me = world["personal"] got = _at(me, draft.check, {"줄": {"이름": NAME, "방식": "직접", "시간당": {"노무비": 10, "재료비": 20, "경비": 30}}}) # fmt: skip assert got["시간당"]["로직"] == "GC001199" and got["시간당"]["계"] == 60 assert got["셈값"] == {"노무비": 10, "재료비": 20, "경비": 30} rec = _at(me, draft.check, {"줄": _body(), "표지": "EQP초안-55"})["연결"] # 화면 계산기 캐시 assert {"키": "EQ:개인 EQP초안-55", "칸": "가격"} in [e["p"] for e in rec["요소"]] assert rec["문서"] and "CC000336" in rec["표"] for raw, code in (({"줄": {"이름": ""}}, 400), ({"줄": _body(), "키": "EQP42-0099"}, 404)): with pytest.raises(store.StoreError) as e: _at(me, draft.check, raw) assert e.value.status == code assert _at(me, mym.rows) == [] def test_brief_same_as_hourly(): """⑧ 기계 창 M01 목록 시간당(`brief`) = 정본 시간당(`hourly`) · 원문번호로도 찾음.""" keys = ["EQ000002", "EQ000010", DIG] rows = mh.brief(keys) assert [r["키"] for r in rows] == keys for r in rows: h = mh.hourly(r["키"]) assert r["시간당"]["ok"] == h["ok"] if h["ok"]: assert r["시간당"]["계"] == h["계"] and r["시간당"]["로직"] == h["로직"] assert mh.brief([rows[0]["원문번호"]])[0]["키"] == "EQ000002"