feat(836-2): 기계 덮음(836-1) 도 고친 항목 — 그 기계 줄 M01 바뀌면 덮음 지움 + 알림 · 문서 다시 복사 때 덮음 유지

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-15 13:37:38 +09:00
co-authored by Claude Opus 5.5
parent 7ef248df24
commit ea95007a32
4 changed files with 107 additions and 3 deletions
+1 -1
View File
@@ -131,7 +131,7 @@ def copy_of(sk: dict, ctx: tuple | None) -> dict | None:
root = {**dc.md.blank(str(sk.get("키"))), "_틀림": str(e)}
return {**got, "문서": root, "부름": {}}
try:
root = derive(sk, ctx)
root = renew.carry(got, derive(sk, ctx)) # 맡긴 기계 덮음(836-2)
except dc._ERR as e:
root = {**dc.md.blank(str(sk.get("키"))), "_틀림": str(e)}
values = dc.values_of(sk[skc.KEY]) if skc.has(sk) else dc.values_of(got) # 서버가 정한 값
+1 -1
View File
@@ -314,7 +314,7 @@ def _formula_job(out: dict, job: Any) -> dict:
if got is None or job not in (PRICE, REGEN):
return out
if job == REGEN:
drop = ("문서", "끊음", "따로", "처음")
drop = ("문서", "끊음", "따로", "처음", "기계덮음")
got = {k: v for k, v in got.items() if k not in drop} | {"부름": {}}
elif got.get("따로"):
got = dc.refresh(got)[0]
+70 -1
View File
@@ -26,6 +26,9 @@ from M02_MasterTemplete import M02_Sangeun_Copy as skc
from M02_MasterTemplete import M02_Sangeun_Doc as sd
from M02_MasterTemplete import M02_Sangeun_Imported as imp
import master_doc_cover as mcv # noqa: E402 — 기계 덮음(836-1 · scripts 길 = dc)
COVER = mcv.COVER # 문서 칸 기계덮음(836-1) — 따로 문서를 다시 복사할 때 `수식` 머리에 맡겨 둠
FIRST = "처음" # `수식` 칸 — 따로가 된 때의 문서(고침 판정 원본)
LOGIC = (
"변수",
@@ -136,6 +139,7 @@ def form(sk: Any, got: Any, notes: list | None = None) -> Any:
if not isinstance(got, dict) or not on() or not is_sk(sk):
return got
out = rows(got) if "로직" in got else dict(got)
out = _covers(got, out, notes)
if imp.is_imported(sk):
return out # 가져온 식 = 값만(식 · 가져온 줄 그대로)
live = dc.folder()
@@ -164,13 +168,78 @@ def form(sk: Any, got: Any, notes: list | None = None) -> Any:
)
if notes is not None:
notes.append(_note(key, first, live[key], mark, "문서", mine.get("이름")))
out = {k: v for k, v in out.items() if k not in ("따로", "문서", FIRST)}
keep = {COVER: mine[COVER]} if mine.get(COVER) else {} # 기계 덮음 = 다른 항목(그대로)
out = {k: v for k, v in out.items() if k not in ("따로", "문서", FIRST, COVER)} | keep
if changed and "문서" in out: # 새 문서가 부르는 키 = 닫힌 묶음
keys = [*dc.calls(out["문서"]), *(c for d in cut.values() for c in dc.calls(d))]
out["부름"] = dc.closure(dc.over(out), keys, calls)
return out
# ── 기계 덮음(836-1) — 그 기계 줄이 M01 에서 바뀌면 덮음 지움 ─────────────
def _eq_rows(copy: dict) -> dict[str, dict]:
"""사본의 기계 줄 {원문번호 · 키: 줄}."""
out: dict[str, dict] = {}
for r in [*(copy.get("로직") or []), *(copy.get("단가") or [])]:
if isinstance(r, dict) and str(r.get("키"))[:2] == "EQ":
out[str(r.get("키"))] = r
if r.get("원문번호"):
out[str(r["원문번호"])] = r
return out
def _mine(entry: dict) -> list[str]:
vals = [f"{k} {v}" for k, v in (entry.get("값") or {}).items()]
crew = [f"조종원 {x.get('키')} {x.get('인')}인" for x in entry.get("조종원") or []]
misc = [
f"잡품 {x.get('이름') or x.get('키') or x.get('줄')} {x.get('율')}%"
for x in entry.get("잡품") or []
]
return [*vals, *crew, *misc] or ["덮음"]
def _row_diff(a: dict, b: dict) -> list[str]:
va, vb = a.get("값") or {}, b.get("값") or {}
if not isinstance(va, dict) or not isinstance(vb, dict):
return [f"{a.get('값')} → {b.get('값')}"]
keys = [*va, *(k for k in vb if k not in va)]
return [f"{k} {va.get(k, '—')} → {vb.get(k, '—')}" for k in keys if va.get(k) != vb.get(k)]
def _covers(got: dict, out: dict, notes: list | None) -> dict:
"""문서(따로) · 맡긴 기계 덮음 가운데 그 기계 줄이 M01 에서 바뀐 것 = 지움 + 알림."""
doc = out.get("문서") if isinstance(out.get("문서"), dict) else None
cover = dict(mcv.cover_of(doc) if doc is not None else out.get(COVER) or {})
if not cover:
return out
was, now = _eq_rows(got), _eq_rows(out)
for text in list(cover):
a, b = was.get(text), now.get(text)
if a is None or b is None or skc._fp(a) == skc._fp(b):
continue
entry = cover.pop(text)
if notes is not None:
name = f"{b.get('이름') or ''} {b.get('규격') or ''}".strip() or text
notes.append(
{
"키": str(b.get("키")),
"이름": name,
"갈래": "기계",
"M01": _row_diff(a, b) or ["바뀜"],
"내고침": _mine(entry),
} # fmt: skip
)
if doc is not None:
rest = {k: v for k, v in doc.items() if k != COVER}
return {**out, "문서": {**rest, COVER: cover} if cover else rest}
return {k: v for k, v in out.items() if k != COVER} | ({COVER: cover} if cover else {})
def carry(got: dict, root: dict) -> dict:
"""파생 문서에 맡긴 기계 덮음을 다시 얹음(따로 문서를 다시 복사한 뒤)."""
return {**root, COVER: got[COVER]} if isinstance(got.get(COVER), dict) and got[COVER] else root
def _note(key: str, was: Any, now: dict, mine: list[str], kind: str, name: Any = None) -> dict:
return {
"키": key,
@@ -211,3 +211,38 @@ def test_imported_values_only(world, monkeypatch):
back = _at(me, lambda: imp.to_price(store.read("sangeun", key)["문서"], "엑셀")["문서"])
_at(me, lambda: store.write("sangeun", key, store.read("sangeun", key)["판"], back))
assert _sum(me, key) == excel
def _cover(me, key: str, cover: dict) -> None:
got = _at(me, path.read, key)
doc = {**copy.deepcopy(got["문서"]), "기계덮음": cover}
_at(me, path.save, key, {"문서": doc, "판": got["판"]})
def test_machine_cover_dropped_when_machine_moves(world, monkeypatch):
"""㉢ 기계 덮음(836-1) + 그 기계 줄 M01 바뀜 = 덮음 지움 + 알림(전 · 후 · 내 고침 값)."""
me = world["personal"]
key = _sk(me)
_stack(me, key, [("기계", MACHINE, "묶음")])
no = MACHINE["입력"]["기계"].strip("'")
_cover(me, key, {no: {"값": {"가격": 150000}}})
assert _at(me, path.read, key)["문서"]["기계덮음"]
_move(monkeypatch, DIG)
got = _at(me, path.read, key)
assert "기계덮음" not in got["문서"]
[note] = got["사본"]["갱신"]
assert note["키"] == DIG and note["갈래"] == "기계"
assert any(x.startswith("가격 ") for x in note["M01"]) and note["내고침"] == ["가격 150000"]
def test_machine_cover_kept_other_item(world, monkeypatch):
"""㉣ 기계 덮음 + 다른 항목(바탕 문서)만 바뀜 = 덮음 그대로(문서 다시 복사 뒤에도)."""
me = world["personal"]
key = _base(me)
_stack(me, key, [("기계", MACHINE, "묶음")])
no = MACHINE["입력"]["기계"].strip("'")
_cover(me, key, {no: {"값": {"가격": 150000}}})
_doc_move(monkeypatch, PCT, _rate("노무비 × 4%"))
got = _at(me, path.read, key)
assert got["문서"]["기계덮음"] == {no: {"값": {"가격": 150000}}}
assert [n["갈래"] for n in got["사본"]["갱신"]] == ["문서"] # 기계 줄 붙인 문서 고침