# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀 """PLAN 804-4 — 줄 쌓기 다른 단가산출 · 일위대가 복사해 펼침(요구 680-1 · 681) — 임시 storage. ① M01 줄 쌓기 = M01 전체만(찾기 창 탭 · 복사 길 없음) · M01 문서 길은 M02 키를 모름 ② M01 단가산출 복사 = 전체 항목 · 줄 · 옵션 굳힘(옵션 0) · 금액 = 원본 · 원본 연결 없음 ③ M02 SK 복사 = 그 SK 수식 문서 금액 · 항목 그대로 ④ 일위대가(SC) 복사 = 항목 여러 개 · 금액 = 일위대가 계 · 고정(노임 · 손 단가) · 율 · 안 SK(× 수량 ÷ 배) ⑤ 앞 줄 있는 문서에 넣음 = 앞 금액 + 원본(율 밑 = 그 조각만 · 끝수 1 원) ⑥ 권한 — 위 층만(남의 개인 층 403) · 저장 = 서버 정본 셈 같음 · M01 노임 바뀜 → [단가 갱신] 뒤 따라감 """ from __future__ import annotations import json from decimal import Decimal from pathlib import Path import pytest from M02_MasterTemplete import M02_Doc_Copy as dc from M02_MasterTemplete import M02_Doc_Sk_Path as path from M02_MasterTemplete import M02_Doc_Take as tk from M02_MasterTemplete import M02_Labor_Ref as refs from M02_MasterTemplete import M02_MasterTemplete_Store as store from resources.tester.test_795_1_sk_copy import _fake, _live, _sk from resources.tester.test_795_2_sc_copy import _sc from resources.tester.test_804_2_line_stack import _ts from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층 ROOT = Path(__file__).resolve().parents[2] FORM = "GC000211" # 합판거푸집(옵션 많음) DIG = "GF000099" # 측구터파기(굴착기 시간당 경비 부름) STONE = "GF000219" # 원/㎡ LB = "LB000002" # 보통인부 ME = {"층": "personal", "주인": "7/42"} def _norm(v): return json.loads(json.dumps(dc.ml._norm(v), ensure_ascii=False, default=str)) def _take(folder, src): with store.at(folder): return _norm(tk.take(src)) def _into(doc, *taken): return _ts("복사", [{"문서": doc, "받음": list(taken)}])[0] def _money(doc): d = json.loads( json.dumps(dc.ml._norm(doc), default=str), parse_float=Decimal, parse_int=Decimal ) return dc.mc.calc(d, dc.ml.Links(_live(), dc.folder())) def _rid(key, at=ME): return refs.ref_id({**at, "키": key}) def _src(rel): return (ROOT / rel).read_text(encoding="utf-8") def test_M01_은_M01전체만(): pick = _src("A00_Common/line_stack_pick.ts") assert 'kinds: copy ? ["logic", "sk", "pre"] : [want], // M01 = M01 전체만(680-1)' in pick assert 'const copy = kind === "단가산출" && m02 ? m02 : null;' in pick lab = _src("M01_MasterData/M01_MasterData_UI_LogicLab.ts") assert ( 'base: "/m01/logic/doc",' in lab and 'cache: "m01_doc_drafts",' in lab ) # 804-7 page = 페이지 [저장] tabs = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Tabs.ts") assert "scope: opt.scope, // 804-4" in tabs screen = _src("A00_Common/line_stack_screen.ts") assert "cp && opt.scope && key" in screen # 복사 길 = M02 저장한 SK 만 # M01 문서 길 = M02 키 모름(부르면 「없는 단가산출」) doc = dc.md.blank("X", "x") doc["변수"] = [ {"기호": "B", "아래": "", "위": "", "연결": {"갈래": "단가산출", "키": _rid("SK-000001")}} ] doc["금액"] = [{"이름": "남", "비목": "묶음", "수량": "1", "단가": "B"}] got = dc.mc.calc(doc, dc.ml.Links(_live(), dc.folder())) assert not got["셈됨"] and "없는 단가산출" in json.dumps(got["표지"], ensure_ascii=False) def test_M01단가산출_복사_전체_옵션굳힘(world): me = world["personal"] t = _take(me, FORM) assert t["출처"] == f"M01 {FORM}" and [p["갈래"] for p in t["조각"]] == ["문서"] g = _into(dc.md.blank("SK-000009", "빈"), t) assert g["옵션"] == [] and g["왕복"] and "번지" not in g["못"] assert g["항목"][0]["줄"][0] == f"글:M01 {FORM} 「{t['이름']}」에서 복사" lines = [x for it in g["항목"] for x in it["줄"]] assert len(lines) > 5 and any(x.startswith("공식:") for x in lines) # 공식 묶음까지 전체 assert _money(g["문서"])["계"] == _money(dc.folder()[FORM])["계"] # 원본 연결 없음 — 복사본에 원본 키 단가산출 연결 없음 · 기호 모두 이 문서 keys = {(v.get("연결") or {}).get("키") for v in g["문서"]["변수"]} assert FORM not in keys def test_SK_복사_그_SK_금액(world): me = world["personal"] key = _sk(me, 바탕={"로직": FORM}, 입력값={"유형": "복잡", "사용횟수": "3회"}) with store.at(me): want = _norm(path.read(key))["셈"]["계"] t = _take(me, _rid(key)) assert t["출처"] == f"개인 {key}" and t["조각"][0]["갈래"] == "문서" g = _into(dc.md.blank("SK-000009", "빈"), t) assert g["옵션"] == [] and _money(g["문서"])["계"] == want # 옵션 「복잡」 그대로 굳힘 assert any(x.get("식") == "'복잡'" for x in g["문서"]["식"]) def _combo(world): me = world["personal"] sk = _sk(me, 바탕={"로직": STONE}) fixed = {"이름": "보통인부", "단위": "인", "단가": {"노무비": 0}, "이음": {"노임": LB}} hand = {"이름": "sub3임시 잡자재", "단위": "식", "단가": {"재료비": 1234}, "이음": {}} rate = {"이름": "공구손료", "단위": "%", "단가": {}, "비율": {"기준": "노무비", "비목": "경비"}} lines = [ {"로직": DIG, "수량": {"손": 2}}, {"로직": "", "고정": fixed, "수량": {"손": 0.3}}, {"로직": "", "고정": hand, "수량": {"손": 2}}, {"참조": {**ME, "키": sk}, "수량": {"손": 3}}, {"로직": "", "고정": rate, "수량": {"손": 3}}, ] return me, sk, _sc(me, lines) def test_일위대가_복사_여러항목_금액같음(world): me, sk, sc = _combo(world) t = _take(me, _rid(sc)) kinds = [p["갈래"] for p in t["조각"]] assert kinds == ["문서", "인력", "손", "문서", "율"], kinds assert t["조각"][0]["곱"] == "2" and t["조각"][3]["곱"] == "3" assert t["조각"][4]["줄"]["단가"] == "노무비 × 3 %" g = _into(dc.md.blank("SK-000009", "빈"), t) assert g["옵션"] == [] and g["왕복"] and "번지" not in g["못"] titles = [it["제목"] for it in g["항목"]] assert titles[0] == t["이름"] and len(g["항목"]) >= 4 # 머리 + 문서 둘 + 줄 조각 항목 got = float(_money(g["문서"])["계"]) # 일위대가 계 — 끝수 차례만 다름(원본 = 문서 계를 버린 뒤 × 수량 · 복사 = 줄마다 × 수량 뒤 버림) assert got == pytest.approx(float(t["계"]), abs=10) # 율 밑 = 그 일위대가 번지 합(앞 줄 섞임 없음) rate = g["문서"]["금액"][-1] assert rate["이름"] == "공구손료" and "번지.복사" in rate["단가"] def test_앞줄있는문서_넣음_율밑안섞임(world): me = world["personal"] gc = dc.folder()[FORM] one = _take(me, DIG) g = _into(gc, one) diff = float(_money(g["문서"])["계"]) - float(_money(gc)["계"]) assert diff == pytest.approx(float(_money(dc.folder()[DIG])["계"]), abs=1) # 일위대가(율 줄 있음) 둘을 잇달아 = 각자 계 합 _, _, sc = _combo(world) t = _take(me, _rid(sc)) two = _into(dc.md.blank("SK-000009", "빈"), t, t) one_sc = float(_money(_into(dc.md.blank("SK-000009", "빈"), t)["문서"])["계"]) assert float(_money(two["문서"])["계"]) == pytest.approx(2 * one_sc, abs=2) def test_권한_저장_단가갱신(world, monkeypatch): me, sk, sc = _combo(world) with pytest.raises(store.StoreError) as e: # 회사 층에서 개인 층 = 위 층 아님 _take(world["company"], _rid(sc)) assert e.value.status == 403 with pytest.raises(store.StoreError): _take(me, "참조|personal|7/43|SC-000001") # 남의 개인 층 # 저장 = 서버 정본 셈 · 다시 읽어 같음 target = _sk(me) # 빈 SK(바탕 · 줄 없음) — 줄 쌓기로 처음부터 복사해 채움 with store.at(me): read = _norm(path.read(target)) g = _into(read["문서"], _take(me, _rid(sc))) with store.at(me): saved = _norm(path.save(target, {"문서": g["문서"], "판": read["판"]})) again = _norm(path.read(target)) assert saved["셈"]["계"] == again["셈"]["계"] == _money(g["문서"])["계"] # 페이지 [저장](옛 칸 · 이름) 뒤에도 고친 문서 그대로(빈 SK 씨로 덮지 않음) with store.at(me): file = store.read("sangeun", target) assert ( file["판"] == saved["판"] ) # 804-8 페이지 [저장] 한 번 = 수식 저장 판 그대로 이어 씀(409 없음) store.write("sangeun", target, file["판"], {**file["문서"], "이름": "sub3임시 이름바꿈"}) again = _norm(path.read(target)) assert again["셈"]["계"] == saved["셈"]["계"] and again["사본"]["따로"] # M01 노임 바뀜 → [단가 갱신] 미리 셈 = 그 자원 단가 따라감(구조 · 품 그대로) — 값 줄 있는 SK(바탕 = 보통인부 든 로직) other = _sk(me, 바탕={"로직": STONE}) with store.at(me): read = _norm(path.read(other)) g = _into(read["문서"], _take(me, _rid(sc))) with store.at(me): again = _norm(path.save(other, {"문서": g["문서"], "판": read["판"]})) row = dict(_live().get(LB)) row["값"] = row["값"] + 1000 _fake(monkeypatch, [row]) with store.at(me): got = _norm(path.preview(other, {"할일": "단가갱신"})) assert any(x["키"] == LB for x in got["바뀐자원"]) assert ( got["후"]["계"] > again["셈"]["계"] + 1000 * 0.3 ) # 보통인부 줄들(복사한 0.3 인 포함) 따라감 assert got["문서"]["금액"] == again["문서"]["금액"] def test_끌어옮김_번지이름(world): """804-5 — 줄 끌기(항목 안 · 사이) · 항목 끌기 = 되돌려 끌면 같은 문서 · 율 줄 번지 = 읽는 이름.""" gc = dc.folder()[DIG] # 항목 둘(인력 · 굴착기) one = _ts("옮김", [{"문서": gc, "옮김": []}])[0] first = one["항목"][0]["줄"][0] gone = _ts("옮김", [{"문서": gc, "옮김": [["0.0", "1.0"]]}])[0] assert ( gone["항목"][1]["줄"][0] == first and len(gone["항목"][0]["줄"]) == len(one["항목"][0]["줄"]) - 1 ) back = _ts("옮김", [{"문서": gc, "옮김": [["0.0", "1.0"], ["1.0", "0.0"]]}])[0] assert back["문서"] == one["문서"] # 같은 항목 안 아래로(빼기 전 차례) — 첫 줄을 끝 자리 = 둘째로 inner = _ts("옮김", [{"문서": gc, "옮김": [["0.0", "0.2"]]}])[0] assert inner["항목"][0]["줄"] == one["항목"][0]["줄"][::-1] n = len(one["항목"]) rot = _ts("옮김", [{"문서": gc, "옮김": [["0", str(n)]]}])[0] assert rot["항목"][-1] == one["항목"][0] and rot["항목"][0] == one["항목"][1] assert ( _money(rot["문서"])["계"] == _money(gc)["계"] ) # 항목만 옮김 = 금액 같음(율 줄은 그 항목 안) # 율 줄 번지 — 일위대가 복사 = 번지마다 읽는 이름(「복사N」 아님) me, sk, sc = _combo(world) g = _into(dc.md.blank("SK-000009", "빈"), _take(me, _rid(sc))) names = _ts("번지", [{"문서": g["문서"]}])[0] places = {m["번지"] for m in g["문서"]["금액"] if m.get("번지")} assert places and set(names) == places assert all(v and not v.startswith("복사") for v in names.values()), names def test_표값_화면이름_전수(): """요구 683 — 표 값 기호(「Q_수량2^표」) = 화면 이름(「Q_작업반장」) · 겹침 없음 · 식 글 왕복 = 같은 식(1,518 전수).""" docs = dc.folder() keys = sorted(docs) got = _ts("별칭", [{"문서": docs[k]} for k in keys]) named = 0 for k, g in zip(keys, got): assert not g["틀림"], (k, g["틀림"][:3]) alias = g["별칭"] assert len(set(alias.values())) == len(alias), k # 겹치면 2 · 3 assert all("^표" not in v and k.endswith("^표") for k, v in alias.items()), k named += len(alias) assert named > 500 gc = dict(zip(keys, got))[FORM]["별칭"] assert gc and "Q_보통인부22" not in gc.values() # 번호 꼬리 뗀 뒤 2 · 3 # 문서 기호는 그대로(화면만) — 원본 문서 변수에 별칭 이름 없음 names = { v["기호"] + (f"_{v['아래']}" if v.get("아래") else "") + (f"^{v['위']}" if v.get("위") else "") for v in docs[FORM]["변수"] } assert not set(gc.values()) & names