- 새 M02_Doc_Gap: 문서 셈이 읽은 요소 · 표 줄 가운데 값 사본에 없는 것(gaps) → 지금 M01 닫힌 묶음으로 더함(fill) - 수식 길(M02_Doc_Sk_Path.edited): 줄 쌓기 품셈 기계 · 바꿔 끼운 기계 · 자원 줄 · 단가산출 참조 · 내 기계 모두 저장 때 박음(옛 _mine 대신) - 저장 첫머리(M02_Doc_Sk.settle): 따로 = [저장] · [단가 갱신] 때 빈틈 메움 · 파생 = 옛 로직 사본에 더함 - 가져온 식 단가기준 우리(M02_Doc_Import): 셈 · 연결 기록 = 로직 사본 위(지금 M01 직접 읽던 빈틈) · 엑셀 기준 그대로 - 읽기 답 사본 표지 「사본없음」 n · 화면 배지 「사본 없는 줄 n」(지금 값으로 셈 · 저장하면 굳음) · 일괄 고쳐 쓰지 않음 - 시험 test_822_1_copy_gap 14 · storage SK 122 고치기 전후 금액 같음 · 빈틈 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
279 lines
12 KiB
Python
279 lines
12 KiB
Python
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
|
"""822-1 사본 빈틈 막기(요구 797-1) — 길마다: 만들기 → [저장] → M01 값(임시 · 메모리 덮개)을
|
|
기계 가격 · 노임 · 유가 · 자재 값으로 바꿈 → 금액 그대로 + 「단가새값」 → [단가 갱신] → 반영.
|
|
|
|
길 = 수식 길 줄 쌓기(품셈 기계 · 바꿔 끼운 기계 · 자원 줄 · 단가산출 참조) ·
|
|
옛 로직 길(더한줄 로직 · 재료 · 인력) · 가져온 식(단가기준 우리) · 층 복사 · [재생성] ·
|
|
빈틈 SK 표시(`사본없음`).
|
|
M01 바꿈 = `skc.Copy` 아닌 진짜 색인 사본(`dc.base` 가 못 벗김) · 정본 파일 안 고침.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_Doc_Sk_Path as path
|
|
from M02_MasterTemplete import M02_Labor_Ref as refs
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
|
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
|
|
|
|
DIG, DIG_NO, DIG_LOGIC = "EQ000022", "0201-0100", "GC000267" # 굴착기(무한궤도) 1.0㎥
|
|
DIG2_NO = "0201-0070" # 바꿔 끼울 굴착기
|
|
MAN = "LB000002" # 보통인부
|
|
PCT = "GC001109" # 금속기와 해체(인력 둘 + 노무비 3 %)
|
|
|
|
|
|
def _live():
|
|
return m01.loaded()[1]
|
|
|
|
|
|
def _move(monkeypatch, *keys: str) -> None:
|
|
"""M01 값 두 배 — 키마다 값 칸(기계 = 가격 · 자재 = 물가 칸) · 진짜 색인 사본(Copy 아님) ·
|
|
수식 문서 폴더 그대로."""
|
|
files, live = m01.loaded()
|
|
fake = copy.copy(live)
|
|
fake.index, fake.numbers = dict(live.index), dict(live.numbers)
|
|
for key in keys:
|
|
tid = key[:2]
|
|
row = copy.deepcopy(live.index[tid][key])
|
|
if tid == "EQ":
|
|
row["값"]["가격"] *= 2
|
|
elif tid == "MT":
|
|
for k in ("물가자료", "유통물가", "물가정보", "거래가격"):
|
|
row[k] = row[k] * 2 if row.get(k) else row.get(k)
|
|
else:
|
|
row["값"] *= 2
|
|
fake.index[tid] = {**fake.index[tid], key: row}
|
|
if row.get("원문번호"):
|
|
fake.numbers[tid] = {**fake.numbers[tid], str(row["원문번호"]): row}
|
|
monkeypatch.setattr(m01, "loaded", lambda: (files, fake))
|
|
|
|
|
|
def _at(folder, fn, *args):
|
|
with store.at(folder):
|
|
return fn(*args)
|
|
|
|
|
|
def _sk(folder, **body) -> str:
|
|
body = {"이름": "sub1임시 빈틈", "단위": "시간", "더한줄": [], **body}
|
|
return _at(folder, lambda: refs.create("sangeun", body)["이름"])
|
|
|
|
|
|
def _var(sym: str, link: dict, name: str = "") -> dict:
|
|
return {"기호": "P", "아래": sym, "위": "", "이름": name or sym, "단위": "", "연결": link}
|
|
|
|
|
|
def _line(sym: str, part: str = "경비") -> dict:
|
|
return {"이름": sym, "단위": "hr", "비목": part, "수량": "1", "단가": f"P_{sym}"}
|
|
|
|
|
|
def _stack(folder, key, rows: list[tuple[str, dict, str]]) -> None:
|
|
"""줄 쌓기 — 지금 문서에 (기호, 연결, 비목) 줄을 더해 수식 길로 저장."""
|
|
got = _at(folder, path.read, key)
|
|
doc = copy.deepcopy(got["문서"])
|
|
doc.setdefault("변수", []).extend(_var(s, link) for s, link, _ in rows)
|
|
doc.setdefault("금액", []).extend(_line(s, part) for s, _, part in rows)
|
|
_at(folder, path.save, key, {"문서": doc, "판": got["판"]})
|
|
|
|
|
|
def _sum(folder, key):
|
|
return _at(folder, path.read, key)["셈"]["계"]
|
|
|
|
|
|
def _price(folder, key) -> dict:
|
|
"""[단가 갱신] 저장 — 바뀐 자원 키 · 뒤 금액."""
|
|
look = _at(folder, path.preview, key, {"할일": skc.PRICE})
|
|
판 = _at(folder, path.read, key)["판"]
|
|
_at(folder, path.save, key, {"할일": skc.PRICE, "판": 판})
|
|
return {"바뀐": sorted(x["키"] for x in look["바뀐자원"]), "후": _sum(folder, key)}
|
|
|
|
|
|
MACHINE = {"갈래": "단가산출", "키": DIG_LOGIC, "입력": {"기계": f"'{DIG_NO}'"}}
|
|
|
|
|
|
def _fuel_and_crew(no: str = DIG_NO) -> tuple[str, str]:
|
|
"""기계 시간당 식이 읽는 유가(MO) · 조종원 노임(LB) 키."""
|
|
w = _live()
|
|
row = w.numbers["EQ"][no]
|
|
fuel = w.numbers["MO"][f"{row['값']['주연료종류']}:전국평균"]["키"]
|
|
crew = w.numbers["LB"][str(row["값"]["조종원직종"])]["키"]
|
|
return fuel, crew
|
|
|
|
|
|
# ── 수식 길 줄 쌓기 ───────────────────────────────────────────────────
|
|
@pytest.mark.parametrize("what", ["가격", "유가", "노임"])
|
|
def test_stack_m01_machine(world, monkeypatch, what):
|
|
"""품셈 기계(굴착기 1.0㎥) 줄 쌓기 → 저장 → 기계 가격 · 유가 · 조종원 노임 바뀜 →
|
|
금액 그대로 + 단가새값 → [단가 갱신] → 반영."""
|
|
me = world["personal"]
|
|
key = _sk(me)
|
|
_stack(me, key, [("기계", MACHINE, "묶음")])
|
|
was = _sum(me, key)
|
|
assert was > 0
|
|
fuel, crew = _fuel_and_crew()
|
|
moved = {"가격": DIG, "유가": fuel, "노임": crew}[what]
|
|
_move(monkeypatch, moved)
|
|
now = _at(me, path.read, key)
|
|
assert now["셈"]["계"] == was and now["사본"]["단가새값"] is True
|
|
got = _price(me, key)
|
|
assert got["바뀐"] == [moved] and got["후"] > was
|
|
|
|
|
|
def test_stack_swapped_machine(world, monkeypatch):
|
|
"""바꿔 끼운 기계 — 기계 입력을 다른 굴착기로 바꿔 저장 → 그 기계 값이 사본에."""
|
|
me = world["personal"]
|
|
key = _sk(me)
|
|
_stack(me, key, [("기계", MACHINE, "묶음")])
|
|
got = _at(me, path.read, key)
|
|
doc = copy.deepcopy(got["문서"])
|
|
for v in doc["변수"]:
|
|
if (v.get("연결") or {}).get("키") == DIG_LOGIC:
|
|
v["연결"]["입력"] = {"기계": f"'{DIG2_NO}'"}
|
|
_at(me, path.save, key, {"문서": doc, "판": got["판"]})
|
|
was = _sum(me, key)
|
|
other = _live().numbers["EQ"][DIG2_NO]["키"]
|
|
_move(monkeypatch, other)
|
|
assert _sum(me, key) == was
|
|
assert _price(me, key)["바뀐"] == [other]
|
|
|
|
|
|
@pytest.mark.parametrize("kind", ["인력", "재료"])
|
|
def test_stack_resource(world, monkeypatch, kind):
|
|
"""자원 줄 더하기(노임 · 자재 요소) — 저장 뒤 값 바뀌어도 그대로 · 갱신 뒤 반영."""
|
|
me = world["personal"]
|
|
res = MAN if kind == "인력" else next(iter(_live().index["MT"]))
|
|
key = _sk(me)
|
|
part = "노무비" if kind == "인력" else "재료비"
|
|
_stack(me, key, [("자원", {"갈래": "요소", "키": res}, part)])
|
|
was = _sum(me, key)
|
|
assert was > 0
|
|
_move(monkeypatch, res)
|
|
assert _sum(me, key) == was
|
|
assert _price(me, key)["바뀐"] == [res]
|
|
|
|
|
|
def test_stack_logic_ref(world, monkeypatch):
|
|
"""단가산출 참조(M01 단가산출을 부름) — 그 식이 읽는 노임도 사본에."""
|
|
me = world["personal"]
|
|
key = _sk(me)
|
|
_stack(me, key, [("참조", {"갈래": "단가산출", "키": PCT, "입력": {}}, "묶음")])
|
|
was = _sum(me, key)
|
|
assert was > 0
|
|
_move(monkeypatch, MAN)
|
|
assert _sum(me, key) == was
|
|
assert MAN in _price(me, key)["바뀐"]
|
|
|
|
|
|
# ── 옛 로직 길(더한줄 · 파생) ────────────────────────────────────────
|
|
OLD_ADDS = {
|
|
"로직 기계": ([{"종류": "로직", "로직": DIG_LOGIC, "입력값": {"기계": DIG_NO}, "수량": 1}],
|
|
{"기계": DIG_NO}, DIG),
|
|
"재료 요소": ([{"종류": "재료", "요소": "MT000008", "수량": 1}], {}, "MT000008"),
|
|
"인력 요소": ([{"종류": "인력", "요소": MAN, "수량": 1}], {}, MAN),
|
|
} # fmt: skip
|
|
|
|
|
|
@pytest.mark.parametrize("name", list(OLD_ADDS))
|
|
def test_old_added_lines(world, monkeypatch, name):
|
|
"""옛 로직 길 더한줄(로직 기계 · 재료 요소 · 인력 요소) — 저장 뒤 그대로 · 갱신 뒤 반영."""
|
|
me = world["personal"]
|
|
adds, given, moved = OLD_ADDS[name]
|
|
key = _sk(me, 더한줄=adds, 입력값=given)
|
|
was = _sum(me, key)
|
|
assert was > 0
|
|
assert _at(me, path.read, key)["사본"]["사본없음"] == 0
|
|
_move(monkeypatch, moved)
|
|
assert _sum(me, key) == was
|
|
got = _price(me, key)
|
|
assert moved in got["바뀐"] and got["후"] > was
|
|
|
|
|
|
# ── 가져온 식 · 층 복사 · 재생성 ──────────────────────────────────────
|
|
def test_imported_ours(world, monkeypatch):
|
|
"""가져온 식 단가기준 우리 — 이음 노임 바뀌어도 그대로 · 갱신 뒤 반영 · 엑셀 기준 = 엑셀 값."""
|
|
from M02_MasterTemplete import M02_Sangeun_Imported as imp
|
|
|
|
me = world["personal"]
|
|
man = {"종류": "인력", "이름": "보통인부", "단위": "인", "수량": "0.5", "비목": "노무비"}
|
|
row = {
|
|
"이름": "sub1임시 가져온",
|
|
"결과단위": "원/㎡",
|
|
"호표": [{**man, "단가": 165545, "이음": MAN}],
|
|
}
|
|
key = _at(me, lambda: imp.create(row, None, "sub1임시 가져온")["이름"])
|
|
assert _sum(me, key) == 82772 # 엑셀 단가 그대로
|
|
doc = _at(me, lambda: store.read("sangeun", key)["문서"])
|
|
ours = _at(me, lambda: imp.to_price(doc, "우리")["문서"])
|
|
_at(me, lambda: store.write("sangeun", key, store.read("sangeun", key)["판"], ours))
|
|
was = _sum(me, key)
|
|
_move(monkeypatch, MAN)
|
|
assert _sum(me, key) == was
|
|
doc = _at(me, lambda: store.read("sangeun", key)["문서"])
|
|
got = _at(me, skc.preview, doc, skc.PRICE)
|
|
assert [x["키"] for x in got["바뀐자원"]] == [MAN] and got["후"]["계"] > was
|
|
|
|
|
|
def test_layer_copy_keeps_copy(world, monkeypatch):
|
|
"""층 복사(개인 → 회사) — 줄 쌓기 기계 사본째 · 원본 M01 값 바뀐 뒤 복사해도 그 값."""
|
|
me, co = world["personal"], world["company"]
|
|
key = _sk(me)
|
|
_stack(me, key, [("기계", MACHINE, "묶음")])
|
|
was = _sum(me, key)
|
|
_move(monkeypatch, DIG)
|
|
ref = {"층": "personal", "주인": "7/42", "키": key}
|
|
with store.at(co):
|
|
doc = refs.read_ref(ref)
|
|
new = refs.create("sangeun", refs.carry(refs.origin_mark(doc, ref), ("personal", "7/42")))
|
|
assert _sum(co, new["이름"]) == was
|
|
assert _at(co, path.read, new["이름"])["사본"]["단가새값"] is True
|
|
|
|
|
|
def test_regen_follows_m01(world, monkeypatch):
|
|
"""[재생성] — 지금 M01 로 다시 찍음(따로 문서 버림) · 그 뒤 M01 바뀌어도 그대로."""
|
|
me = world["personal"]
|
|
key = _sk(me, 바탕={"로직": PCT}, 단위="㎡")
|
|
_stack(me, key, [("기계", MACHINE, "묶음")])
|
|
_move(monkeypatch, MAN)
|
|
판 = _at(me, path.read, key)["판"]
|
|
_at(me, path.save, key, {"할일": skc.REGEN, "판": 판})
|
|
after = _at(me, path.read, key)
|
|
assert after["사본"]["따로"] is False and after["사본"]["사본없음"] == 0
|
|
assert not after["사본"]["단가새값"]
|
|
was = after["셈"]["계"]
|
|
_move(monkeypatch, MAN) # 한 번 더(네 배)
|
|
assert _sum(me, key) == was
|
|
|
|
|
|
def test_gap_mark_then_save_fills(world, monkeypatch):
|
|
"""빈틈 있는 옛 SK — 열면 지금 값으로 셈 + 「사본없음 n」 · 일괄 고쳐 쓰지 않음 ·
|
|
[저장] 하면 지금 값으로 굳음(n = 0) · 그 뒤 M01 바뀌어도 그대로."""
|
|
me = world["personal"]
|
|
key = _sk(me)
|
|
_stack(me, key, [("기계", MACHINE, "묶음")])
|
|
file = me / "sangeun" / f"{key}.json"
|
|
raw = json.loads(file.read_text(encoding="utf-8"))
|
|
vals = raw["문서"]["수식"] if "문서" in raw else raw["수식"]
|
|
for part in ("로직", "단가"):
|
|
vals[part] = [] # 옛 저장본 흉내 — 줄 쌓기 기계를 사본에 안 박은 때
|
|
file.write_text(json.dumps(raw, ensure_ascii=False), encoding="utf-8")
|
|
before = file.read_bytes()
|
|
got = _at(me, path.read, key)
|
|
assert got["사본"]["사본없음"] > 0
|
|
was = got["셈"]["계"]
|
|
assert file.read_bytes() == before # 열기 = 안 고쳐 씀
|
|
files, live = m01.loaded()
|
|
_move(monkeypatch, DIG)
|
|
moved = _sum(me, key)
|
|
assert moved > was # 빈틈 = 지금 값으로 셈
|
|
got = _at(me, path.read, key)
|
|
_at(me, path.save, key, {"문서": got["문서"], "판": got["판"]})
|
|
now = _at(me, path.read, key)
|
|
assert now["사본"]["사본없음"] == 0 and now["셈"]["계"] == moved
|
|
monkeypatch.setattr(m01, "loaded", lambda: (files, live)) # 앞 값(절반)
|
|
assert _sum(me, key) == moved
|