feat(796-2e-1): 더한 기초 블록 [연결 끊기] · [다시 잇기] 서버 — 끊음 = 이 SK 안 자기 사본 줄 · 수식 고침 · 재생성 때 줄 안 바꿈
- M02_Sangeun_Block 신설: 끊기(닫힌 묶음 굳힘 · 금액 그대로) · 다시 잇기(고침 버림 + 원본 갈음) · 끊은 기초 수식 읽기 · 미리 셈 · 저장 검사 - Calc.sub_run 이 끊은 기초 자기 줄 + 수식 고침을 얹어 셈 · Copy.settle 이 다시 잇기 표지 처리 · [단가 갱신] · [재생성] 응답 끊은기초 한 줄 - Router: block/unlink · relink · equation 몸 블록 · 새 시험 test_796_2e_unlink_basis 6 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,212 @@
|
|||||||
|
"""M02 단가산출(SK) 더한 기초 블록 [연결 끊기] · [다시 잇기](PLAN 796-2e · 요구 659-3 · 656-1).
|
||||||
|
|
||||||
|
더한 기초 = 더한줄 로직(`종류: 로직`). 기본 = 연결 — 이 SK 의 로직 사본(`M02_Sangeun_Copy`)이 닿는 그 기초
|
||||||
|
(원본 바뀜은 [재생성]·[다시 잇기]에서 따라옴 · 자동 아님 · 보기만).
|
||||||
|
끊음 = 더한줄 칸 `끊음` {시점, 로직 [M01 줄]} — 끊는 때 그 기초가 닿는 닫힌 묶음(하위 로직 · 찾기 표 ·
|
||||||
|
요율 · 사본 줄 그대로 = 금액 그대로)을 이 SK 안 자기 사본으로 굳힘 → 그 줄만 고침:
|
||||||
|
칸 `수식고침`(796-2b 와 같은 꼴 · 같은 `eqm.apply`)을 그 기초 줄에 얹어 셈(`Calc.sub_run`).
|
||||||
|
[재생성] = 끊은 기초의 줄은 안 바꿈(자원 단가만 — 단가는 SK 사본 `단가` · [단가 갱신] 한 길).
|
||||||
|
다시 잇기 = `끊음` · `수식고침` 뗌 + 표지 `다시잇기`(서버만 · 셈·저장 첫머리 `Copy.settle` 이 그 기초가 닿는
|
||||||
|
닫힌 묶음을 지금 M01 줄로 갈음하고 표지 뗌 — 고친 것 버림).
|
||||||
|
길(`M02_Sangeun_Router`): POST …/sangeun/block/{unlink|relink} {문서, 차례} → {문서, 전, 후}(저장 없음) ·
|
||||||
|
POST …/sangeun/equation[/preview] 몸 `블록` = 더한줄 차례 → 그 끊은 기초의 수식(고침 통째 미리 셈).
|
||||||
|
저장 = SK [저장](서버가 다시 얹어 검사 · 셈 · 브라우저 값 안 받음 — 끊음 줄은 사본과 같은 `taken` 꼴 검사).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from M02_MasterTemplete import M02_Item_Prices as prices
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Equation as eqm
|
||||||
|
|
||||||
|
CUT, EDIT, REJOIN = "끊음", eqm.KEY, "다시잇기"
|
||||||
|
MANY = 400 # 끊은 기초가 닿는 줄 상한
|
||||||
|
_OWN: dict[int, tuple[Any, Any, Any]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def is_block(one: Any) -> bool:
|
||||||
|
"""더한 기초 = 더한줄 로직(참조 · 고정 · 자원 줄 아님)."""
|
||||||
|
return isinstance(one, dict) and one.get("종류") == "로직" and bool(one.get("로직"))
|
||||||
|
|
||||||
|
|
||||||
|
def clean(raw: dict, where: str) -> dict:
|
||||||
|
"""`sd._added` 가 부름 — 끊음 · 수식고침 꼴만(뜻 검사는 셈 · 저장 `strict`)."""
|
||||||
|
cut = raw.get(CUT)
|
||||||
|
if not cut:
|
||||||
|
if raw.get(EDIT):
|
||||||
|
raise sd.DocError(f"{where} · 수식 고침은 연결을 끊은 기초만")
|
||||||
|
return {}
|
||||||
|
rows = cut.get("로직") if isinstance(cut, dict) else None
|
||||||
|
code = str(raw.get("로직"))
|
||||||
|
if any(raw.get(k) not in (None, {}, []) for k in sd.BODY):
|
||||||
|
raise sd.DocError(f"{where} · 넣은 SK 몸은 이미 이 SK 안 사본 — 연결 끊기 없음")
|
||||||
|
if (
|
||||||
|
not isinstance(rows, list)
|
||||||
|
or len(rows) > MANY
|
||||||
|
or not all(isinstance(r, dict) and r.get("키") for r in rows)
|
||||||
|
):
|
||||||
|
raise sd.DocError(f"{where} · 끊은 기초 사본 꼴이 틀림")
|
||||||
|
rows = [r for r in rows if str(r["키"])[:2] not in skc.RES] # 단가 = SK 사본 한 곳
|
||||||
|
if code not in {str(r["키"]) for r in rows}:
|
||||||
|
raise sd.DocError(f"{where} · 끊은 기초 사본에 「{code}」 줄 없음")
|
||||||
|
return {CUT: {"시점": str(cut.get("시점") or ""), "로직": rows}} | eqm.clean(raw, {})
|
||||||
|
|
||||||
|
|
||||||
|
def own(one: dict, whole: Any) -> Any:
|
||||||
|
"""끊은 기초의 셈 색인 — `whole` 위에 자기 사본 줄(끊음 아님 = `whole` 그대로)."""
|
||||||
|
cut = one.get(CUT)
|
||||||
|
if not isinstance(cut, dict):
|
||||||
|
return whole
|
||||||
|
hit = _OWN.get(id(cut))
|
||||||
|
if hit and hit[0] is cut and hit[1] is whole:
|
||||||
|
return hit[2]
|
||||||
|
if len(_OWN) > 32: # ponytail: 통째 비움 · 끊은 기초가 많아 느려지면 LRU 로
|
||||||
|
_OWN.clear()
|
||||||
|
got = skc.Copy(whole, skc._rows(cut))
|
||||||
|
_OWN[id(cut)] = (cut, whole, got)
|
||||||
|
return got
|
||||||
|
|
||||||
|
|
||||||
|
def row_of(whole: Any, one: dict, edits: list | None = None) -> tuple[dict, list[str], Any]:
|
||||||
|
"""(끊은 기초 줄 + 수식 고침, 못 얹은 까닭, 그 기초 셈 색인) — `edits` None = 그 기초 자기 고침."""
|
||||||
|
mine = own(one, whole)
|
||||||
|
row = mine.logic(str(one["로직"]))
|
||||||
|
use = one.get(EDIT) if edits is None else edits
|
||||||
|
if not use:
|
||||||
|
return row, [], mine
|
||||||
|
new, why = eqm.apply(row, use, mine)
|
||||||
|
return new, why, mine
|
||||||
|
|
||||||
|
|
||||||
|
def strict(doc: dict, whole: Any) -> None:
|
||||||
|
"""저장 — 끊은 기초 고침이 못 얹히면 400."""
|
||||||
|
for at, one in enumerate(doc.get("더한줄") or [], 1):
|
||||||
|
if is_block(one) and one.get(EDIT):
|
||||||
|
why = row_of(whole, one)[1]
|
||||||
|
if why:
|
||||||
|
raise sd.DocError(f"더한줄 {at}째 수식 고침 못 얹음 — " + " · ".join(why))
|
||||||
|
|
||||||
|
|
||||||
|
def names(doc: Any) -> list[str]:
|
||||||
|
"""끊은 기초 이름들([단가 갱신] · [재생성] 보고)."""
|
||||||
|
adds = (doc.get("더한줄") if isinstance(doc, dict) else None) or []
|
||||||
|
return [str(x.get("이름") or x.get("로직")) for x in adds if is_block(x) and x.get(CUT)]
|
||||||
|
|
||||||
|
|
||||||
|
# ── 끊기 · 다시 잇기 ───────────────────────────────────────────────────
|
||||||
|
def _one(doc: Any, at: Any) -> dict:
|
||||||
|
adds = (doc.get("더한줄") if isinstance(doc, dict) else None) or []
|
||||||
|
if not isinstance(at, int) or isinstance(at, bool) or not 0 <= at < len(adds):
|
||||||
|
raise sd.DocError(f"더한 기초 차례 「{at}」 없음")
|
||||||
|
if not is_block(adds[at]):
|
||||||
|
raise sd.DocError("더한줄이 기초(로직) 아님")
|
||||||
|
return adds[at]
|
||||||
|
|
||||||
|
|
||||||
|
def _put(doc: dict, at: int, one: dict) -> dict:
|
||||||
|
return {**doc, "더한줄": [one if k == at else x for k, x in enumerate(doc["더한줄"])]}
|
||||||
|
|
||||||
|
|
||||||
|
def _closure(doc: dict, code: str) -> list[dict]:
|
||||||
|
"""`code` 가 닿는 닫힌 묶음(자원 단가 빼고) — 사본 있으면 사본 줄 그대로 · 없으면 지금 M01."""
|
||||||
|
over = doc[skc.KEY]["로직"] if skc.has(doc) else None
|
||||||
|
got = prices.collect([code], None, None, over)
|
||||||
|
got.pop("_빠진", None)
|
||||||
|
whole = skc.over(doc, skc.whole_())
|
||||||
|
keys = [str(r.get("키")) for data in got.values() for r in data[prices.m01.items_key(data)]]
|
||||||
|
rows = [
|
||||||
|
whole.index[k[:2]][k]
|
||||||
|
for k in keys
|
||||||
|
if k[:2] not in skc.RES and k in whole.index.get(k[:2], {})
|
||||||
|
]
|
||||||
|
return prices.to_json(rows)
|
||||||
|
|
||||||
|
|
||||||
|
def unlink(doc: Any, at: int) -> dict:
|
||||||
|
"""[연결 끊기] — 그 기초가 닿는 닫힌 묶음을 이 SK 안에 굳힘(금액 그대로)."""
|
||||||
|
one = _one(doc, at)
|
||||||
|
if one.get(CUT):
|
||||||
|
raise sd.DocError("이미 연결을 끊은 기초")
|
||||||
|
if any(one.get(k) not in (None, {}, []) for k in sd.BODY):
|
||||||
|
raise sd.DocError("넣은 SK 몸은 이미 이 SK 안 사본 — 연결 끊기 없음")
|
||||||
|
rows = _closure(skc.settle(doc), str(one["로직"]))
|
||||||
|
if not rows:
|
||||||
|
raise sd.DocError(f"기초 「{one['로직']}」 줄을 못 찾음")
|
||||||
|
cut = {"시점": datetime.now().isoformat(timespec="seconds"), "로직": rows}
|
||||||
|
return _put(doc, at, {**one, CUT: cut})
|
||||||
|
|
||||||
|
|
||||||
|
def relink(doc: Any, at: int) -> dict:
|
||||||
|
"""[다시 잇기] — 끊음 · 고침 버림 + 표지(셈·저장 때 사본을 지금 M01 줄로 갈음)."""
|
||||||
|
one = _one(doc, at)
|
||||||
|
if not one.get(CUT):
|
||||||
|
raise sd.DocError("연결된 기초")
|
||||||
|
keep = {k: v for k, v in one.items() if k not in (CUT, EDIT)}
|
||||||
|
return _put(doc, at, {**keep, REJOIN: True})
|
||||||
|
|
||||||
|
|
||||||
|
def change(doc: Any, at: Any, how: str) -> dict:
|
||||||
|
"""길 몸 — {문서, 전, 후}(전후 SK 금액 · 끊기 전후 같음)."""
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Equation_Full as full
|
||||||
|
|
||||||
|
new = (unlink if how == "unlink" else relink)(doc, at)
|
||||||
|
money = [full.total(full.answer(d, None)) for d in (doc, new)]
|
||||||
|
return {"문서": new, "전": money[0], "후": money[1]}
|
||||||
|
|
||||||
|
|
||||||
|
# ── 수식 탭 ────────────────────────────────────────────────────────────
|
||||||
|
def _setup(doc: Any, at: Any) -> tuple[dict, Any, dict]:
|
||||||
|
if not isinstance(doc, dict):
|
||||||
|
raise sd.DocError("단가산출 문서가 묶음 아님")
|
||||||
|
one = _one(doc, at)
|
||||||
|
if not one.get(CUT):
|
||||||
|
raise sd.DocError("연결된 기초 — 먼저 [연결 끊기]")
|
||||||
|
doc = skc.settle(doc)
|
||||||
|
return doc, skc.over(doc, skc.whole_()), doc["더한줄"][at]
|
||||||
|
|
||||||
|
|
||||||
|
def view(whole: Any, doc: dict, one: dict, edits: list | None, inputs: dict | None) -> tuple:
|
||||||
|
"""(수식, 못 얹은 까닭) — 고친 줄을 그대로 그림(세부 덮개 · 뺀줄 얹기 전 = 고침 주소 그대로)."""
|
||||||
|
row, why, mine = row_of(whole, one, edits)
|
||||||
|
if why:
|
||||||
|
return None, why
|
||||||
|
code = str(one["로직"])
|
||||||
|
given = {k: v for k, v in (one.get("입력값") or {}).items() if str(v).strip()}
|
||||||
|
given = {**(doc.get("입력값") or {}), **(inputs or {}), **given}
|
||||||
|
return eqm.export(mine, {**row, "키": code}, given), []
|
||||||
|
|
||||||
|
|
||||||
|
def read(doc: Any, at: Any, inputs: dict | None = None) -> dict[str, Any]:
|
||||||
|
"""{ok, 수식 | reason} — 끊은 기초 수식(자기 고침 얹음)."""
|
||||||
|
doc, whole, one = _setup(doc, at)
|
||||||
|
eq, why = view(whole, doc, one, None, inputs)
|
||||||
|
return {"ok": False, "reason": " · ".join(why)} if why else {"ok": True, "수식": eq}
|
||||||
|
|
||||||
|
|
||||||
|
def preview(doc: Any, at: Any, edits: list, inputs: dict | None = None) -> dict[str, Any]:
|
||||||
|
"""고침 통째 미리 셈(저장 없음) — {되나, 까닭, 줄, 수식, 전(기초 고침 없음), 후, 금액전, 금액후(SK)}."""
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Equation_Full as full
|
||||||
|
|
||||||
|
doc, whole, one = _setup(doc, at)
|
||||||
|
eq, why = view(whole, doc, one, edits, inputs)
|
||||||
|
if why:
|
||||||
|
return {"되나": False, "까닭": why}
|
||||||
|
bare = {k: v for k, v in one.items() if k != EDIT}
|
||||||
|
mine = {**bare, EDIT: edits} if edits else bare
|
||||||
|
plain = view(whole, doc, bare, None, inputs)[0] if edits else eq
|
||||||
|
row = row_of(whole, bare, edits)[0]
|
||||||
|
money = [full.total(full.answer(d, inputs)) for d in (doc, _put(doc, at, mine))]
|
||||||
|
return {
|
||||||
|
"되나": True,
|
||||||
|
"까닭": [],
|
||||||
|
"줄": {**row, "입력": []}, # 입력 칸 = SK 쪽 것만(끊은 기초 입력은 더한 기초 자리에서)
|
||||||
|
"수식": eq,
|
||||||
|
"전": plain["합계"],
|
||||||
|
"후": eq["합계"],
|
||||||
|
"금액전": money[0],
|
||||||
|
"금액후": money[1],
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ from typing import Any
|
|||||||
from M02_MasterTemplete import M02_Labor_Doc as labor, M02_Labor_Fixed as fixed
|
from M02_MasterTemplete import M02_Labor_Doc as labor, M02_Labor_Fixed as fixed
|
||||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Block as blk
|
||||||
from M02_MasterTemplete import M02_Sangeun_Const as const, M02_Sangeun_Copy as skc
|
from M02_MasterTemplete import M02_Sangeun_Const as const, M02_Sangeun_Copy as skc
|
||||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd, M02_Sangeun_Equation as eqm
|
from M02_MasterTemplete import M02_Sangeun_Doc as sd, M02_Sangeun_Equation as eqm
|
||||||
from M02_MasterTemplete import M02_Sangeun_Views as views
|
from M02_MasterTemplete import M02_Sangeun_Views as views
|
||||||
@@ -247,8 +248,9 @@ def sub_run(one: dict, whole: Any, patched: bool = True) -> dict[str, Any]:
|
|||||||
짝잃음 ·
|
짝잃음 ·
|
||||||
바탕판(덮기 전 기초 지문) · 입력 = 채운 줄 값(`_fill`). `patched` False = 덮기 전."""
|
바탕판(덮기 전 기초 지문) · 입력 = 채운 줄 값(`_fill`). `patched` False = 덮기 전."""
|
||||||
code = str(one["로직"])
|
code = str(one["로직"])
|
||||||
|
mine = blk.own(one, whole) # 끊은 기초(796-2e) = 자기 사본 줄 위
|
||||||
try:
|
try:
|
||||||
row = whole.logic(code)
|
row = mine.logic(code)
|
||||||
except Exception as e: # noqa: BLE001 — 엔진 FormulaError
|
except Exception as e: # noqa: BLE001 — 엔진 FormulaError
|
||||||
return {"기준": None, "까닭": str(e), "정체": [], "짝잃음": [], "바탕판": ""}
|
return {"기준": None, "까닭": str(e), "정체": [], "짝잃음": [], "바탕판": ""}
|
||||||
if any(one.get(k) not in (None, {}, []) for k in sd.BODY): # 넣은 SK 몸(740-1) = 이름 없는 SK
|
if any(one.get(k) not in (None, {}, []) for k in sd.BODY): # 넣은 SK 몸(740-1) = 이름 없는 SK
|
||||||
@@ -256,13 +258,15 @@ def sub_run(one: dict, whole: Any, patched: bool = True) -> dict[str, Any]:
|
|||||||
doc = {"키": "SK-000000", "이름": "줄", "단위": "식", "바탕": {"로직": code}}
|
doc = {"키": "SK-000000", "이름": "줄", "단위": "식", "바탕": {"로직": code}}
|
||||||
doc |= {"덮개": {}, "뺀줄": [], "더한줄": [], **{k: one[k] for k in keep if k in one}}
|
doc |= {"덮개": {}, "뺀줄": [], "더한줄": [], **{k: one[k] for k in keep if k in one}}
|
||||||
try:
|
try:
|
||||||
return {**run(doc, whole), "바탕판": fingerprint(row)}
|
return {**run(doc, mine), "바탕판": fingerprint(row)}
|
||||||
except sd.DocError as e:
|
except sd.DocError as e:
|
||||||
return {"기준": None, "까닭": str(e), "정체": [], "짝잃음": [], "바탕판": ""}
|
return {"기준": None, "까닭": str(e), "정체": [], "짝잃음": [], "바탕판": ""}
|
||||||
got = sd.patched(row, one if patched else {})
|
edited, lost = blk.row_of(whole, one)[0:2] if patched else (row, []) # 수식 고침(796-2e)
|
||||||
over = _Over(whole, code, {**got["줄"], "키": code})
|
got = sd.patched(edited, one if patched else {})
|
||||||
|
over = _Over(mine, code, {**got["줄"], "키": code})
|
||||||
out = labor.run_line(over, code, one.get("입력값"), None, got["고름"] or None)
|
out = labor.run_line(over, code, one.get("입력값"), None, got["고름"] or None)
|
||||||
return {**out, "정체": got["정체"], "짝잃음": got["짝잃음"], "바탕판": fingerprint(row)}
|
miss = [{"cid": eqm.LOST, "까닭": "수식 고침 못 얹음 — " + " · ".join(lost)}] if lost else []
|
||||||
|
return {**out, "정체": got["정체"], "짝잃음": got["짝잃음"] + miss, "바탕판": fingerprint(row)}
|
||||||
|
|
||||||
|
|
||||||
def _bundled(item: dict, sub: dict) -> dict:
|
def _bundled(item: dict, sub: dict) -> dict:
|
||||||
@@ -548,6 +552,7 @@ def clean_doc(doc: Any, here_: refs.Here) -> dict[str, Any]:
|
|||||||
rough = sd.clean(doc, None, whole)
|
rough = sd.clean(doc, None, whole)
|
||||||
_check_refs(rough, here_)
|
_check_refs(rough, here_)
|
||||||
eqm.strict(base := _base_of(rough, whole, refs.read_ref, ())) # 수식 고침(796-2b)
|
eqm.strict(base := _base_of(rough, whole, refs.read_ref, ())) # 수식 고침(796-2b)
|
||||||
|
blk.strict(rough, whole) # 끊은 기초 수식 고침(796-2e)
|
||||||
out = sd.clean(doc, base["줄"], whole)
|
out = sd.clean(doc, base["줄"], whole)
|
||||||
out["바탕"], out["더한줄"] = rough["바탕"], rough["더한줄"]
|
out["바탕"], out["더한줄"] = rough["바탕"], rough["더한줄"]
|
||||||
for one in out["더한줄"]: # 처음수량(744-1) = 넣을 때 수량 · 없으면 지금 수량
|
for one in out["더한줄"]: # 처음수량(744-1) = 넣을 때 수량 · 없으면 지금 수량
|
||||||
|
|||||||
@@ -209,6 +209,19 @@ def grow(copy: dict, fresh: dict | None) -> dict:
|
|||||||
return {**copy, "로직": logic, "판": _fp(logic), "단가": cost, "단가판": _fp(cost)}
|
return {**copy, "로직": logic, "판": _fp(logic), "단가": cost, "단가판": _fp(cost)}
|
||||||
|
|
||||||
|
|
||||||
|
def rejoin(copy: dict, adds: list) -> dict:
|
||||||
|
"""[다시 잇기](796-2e) — 표지 낀 더한 기초가 닿는 닫힌 묶음을 지금 M01 줄로 갈음(단가 줄은 더하기만)."""
|
||||||
|
keys = [
|
||||||
|
str(x["로직"]) for x in adds if isinstance(x, dict) and x.get("다시잇기") and x.get("로직")
|
||||||
|
]
|
||||||
|
fresh = snap(keys, [])
|
||||||
|
if not fresh:
|
||||||
|
return copy
|
||||||
|
new = {str(r.get("키")): r for r in fresh["로직"]}
|
||||||
|
logic = [new.pop(str(r.get("키")), r) for r in copy["로직"]] + list(new.values())
|
||||||
|
return grow({**copy, "로직": logic, "판": _fp(logic)}, fresh)
|
||||||
|
|
||||||
|
|
||||||
def taken(raw: Any) -> dict | None:
|
def taken(raw: Any) -> dict | None:
|
||||||
"""받은 사본(층 복사 · 새로) — 줄 꼴만 보고 판은 서버가 다시 셈."""
|
"""받은 사본(층 복사 · 새로) — 줄 꼴만 보고 판은 서버가 다시 셈."""
|
||||||
if not has({KEY: raw}):
|
if not has({KEY: raw}):
|
||||||
@@ -244,6 +257,8 @@ def settle(doc: Any, save: bool = False) -> Any:
|
|||||||
copy = stamp(out)
|
copy = stamp(out)
|
||||||
elif job == PRICE and copy:
|
elif job == PRICE and copy:
|
||||||
copy = refresh(copy, whole_())[0]
|
copy = refresh(copy, whole_())[0]
|
||||||
|
if copy and any(isinstance(x, dict) and x.get("다시잇기") for x in out.get("더한줄") or []):
|
||||||
|
copy = rejoin(copy, out["더한줄"])
|
||||||
if save and copy:
|
if save and copy:
|
||||||
copy = grow(copy, stamp(out))
|
copy = grow(copy, stamp(out))
|
||||||
return {**out, KEY: copy} if copy else out
|
return {**out, KEY: copy} if copy else out
|
||||||
@@ -311,6 +326,10 @@ def preview(doc: Any, job: str, given: dict | None = None) -> dict[str, Any]:
|
|||||||
before = calc.calc_doc(doc, given, False)
|
before = calc.calc_doc(doc, given, False)
|
||||||
after = calc.calc_doc(after_doc, given, False)
|
after = calc.calc_doc(after_doc, given, False)
|
||||||
out: dict[str, Any] = {"문서": after_doc, "전": _money(before), "후": _money(after)}
|
out: dict[str, Any] = {"문서": after_doc, "전": _money(before), "후": _money(after)}
|
||||||
|
if cut := [
|
||||||
|
str(x.get("이름") or x.get("로직")) for x in doc.get("더한줄") or [] if x.get("끊음")
|
||||||
|
]:
|
||||||
|
out["끊은기초"] = cut # 줄은 그대로 · 자원 단가만(요구 656-1)
|
||||||
if job == PRICE:
|
if job == PRICE:
|
||||||
copy = _stored(doc.get("키")) or taken(doc.get(KEY))
|
copy = _stored(doc.get("키")) or taken(doc.get(KEY))
|
||||||
out["바뀐자원"] = refresh(copy, whole_())[1] if copy else []
|
out["바뀐자원"] = refresh(copy, whole_())[1] if copy else []
|
||||||
|
|||||||
@@ -183,7 +183,9 @@ def _added(raw: Any, where: str, whole: Any) -> dict:
|
|||||||
if row is not None:
|
if row is not None:
|
||||||
head["입력값"] = {k: v for k, v in given.items() if k in names}
|
head["입력값"] = {k: v for k, v in given.items() if k in names}
|
||||||
name = name or str(row.get("이름") or code)
|
name = name or str(row.get("이름") or code)
|
||||||
head |= _sub_patch(raw, where) | _sub_body(raw, where, whole)
|
from M02_MasterTemplete import M02_Sangeun_Block as blk # 연결 끊기(796-2e)
|
||||||
|
|
||||||
|
head |= _sub_patch(raw, where) | _sub_body(raw, where, whole) | blk.clean(raw, where)
|
||||||
elif raw.get("종류") in ADD_PART:
|
elif raw.get("종류") in ADD_PART:
|
||||||
key = str(raw.get("요소") or "")
|
key = str(raw.get("요소") or "")
|
||||||
got = _element(whole, key, where)
|
got = _element(whole, key, where)
|
||||||
@@ -253,8 +255,8 @@ def _sub_body(raw: dict, where: str, whole: Any) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def sub_patched(one: dict) -> bool:
|
def sub_patched(one: dict) -> bool:
|
||||||
"""고친 둘째 기초 — 더한줄 로직에 세부 덮개 · 뺀줄 · 넣은 SK 몸(740-1)."""
|
"""고친 둘째 기초 — 더한줄 로직에 세부 덮개 · 뺀줄 · 넣은 SK 몸(740-1) · 끊은 기초(796-2e)."""
|
||||||
return one.get("종류") == "로직" and any(one.get(k) for k in ("덮개", "뺀줄", *BODY))
|
return one.get("종류") == "로직" and any(one.get(k) for k in ("덮개", "뺀줄", "끊음", *BODY))
|
||||||
|
|
||||||
|
|
||||||
def _added_names(whole: Any, adds: Any) -> set[str]:
|
def _added_names(whole: Any, adds: Any) -> set[str]:
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ def rows(doc: dict, ans: dict, tree: Any, out_ids: set[str] = frozenset()) -> li
|
|||||||
k = int(hit[1]) if hit else -1
|
k = int(hit[1]) if hit else -1
|
||||||
state = "더함" if hit else "덮음" if cover else "바탕"
|
state = "더함" if hit else "덮음" if cover else "바탕"
|
||||||
one = adds[k] if 0 <= k < len(adds) else {}
|
one = adds[k] if 0 <= k < len(adds) else {}
|
||||||
if hit and one.get("종류") == "로직" and sd.sub_patched(one):
|
if hit and one.get("종류") == "로직" and sd.sub_patched({**one, "끊음": None}):
|
||||||
cover = cover or ["세부"]
|
cover = cover or ["세부"]
|
||||||
outside = cid in out_ids # 합계 밖 줄(가져온 식 `합밖` · 제외 단계) — 합에 안 듦
|
outside = cid in out_ids # 합계 밖 줄(가져온 식 `합밖` · 제외 단계) — 합에 안 듦
|
||||||
out.append(
|
out.append(
|
||||||
@@ -116,6 +116,8 @@ def rows(doc: dict, ans: dict, tree: Any, out_ids: set[str] = frozenset()) -> li
|
|||||||
원래=info.get("원래") if state == "덮음" else None,
|
원래=info.get("원래") if state == "덮음" else None,
|
||||||
까닭=line.get("까닭") or info.get("까닭"),
|
까닭=line.get("까닭") or info.get("까닭"),
|
||||||
나무=tree(k) if hit and one.get("종류") == "로직" else None,
|
나무=tree(k) if hit and one.get("종류") == "로직" else None,
|
||||||
|
블록=k if hit and one.get("종류") == "로직" else None, # 더한 기초 차례(연결 끊기)
|
||||||
|
끊음=True if one.get("끊음") else None,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for cid in doc.get("뺀줄") or []: # 지운 줄 — 금액 0 · 원래 금액만 보임
|
for cid in doc.get("뺀줄") or []: # 지운 줄 — 금액 0 · 원래 금액만 보임
|
||||||
@@ -152,6 +154,10 @@ def _basic_tree(whole: Any, doc: dict, one: dict, inputs: dict | None) -> dict |
|
|||||||
|
|
||||||
if any(one.get(k) not in (None, {}, []) for k in sd.BODY):
|
if any(one.get(k) not in (None, {}, []) for k in sd.BODY):
|
||||||
return None
|
return None
|
||||||
|
if one.get("끊음"): # 끊은 기초(796-2e) = 자기 사본 줄 + 수식 고침(고침 주소 그대로)
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Block as blk
|
||||||
|
|
||||||
|
return blk.view(whole, doc, one, None, inputs)[0]
|
||||||
code = str(one["로직"])
|
code = str(one["로직"])
|
||||||
mine = {k: v for k, v in (one.get("입력값") or {}).items() if str(v).strip()}
|
mine = {k: v for k, v in (one.get("입력값") or {}).items() if str(v).strip()}
|
||||||
given = {**(doc.get("입력값") or {}), **(inputs or {}), **mine}
|
given = {**(doc.get("입력값") or {}), **(inputs or {}), **mine}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
(화면 캐시 문서에 할일 얹음 · 저장 없음 · 층 읽기).
|
(화면 캐시 문서에 할일 얹음 · 저장 없음 · 층 읽기).
|
||||||
수식 탭(796-2b · `M02_Sangeun_Equation`) `…/sangeun/equation`(층 읽기) · `/preview`(고침 미리 셈 ·
|
수식 탭(796-2b · `M02_Sangeun_Equation`) `…/sangeun/equation`(층 읽기) · `/preview`(고침 미리 셈 ·
|
||||||
층 쓰기) — 저장 없음 · 고침 = 화면 캐시 `수식고침` → [저장].
|
층 쓰기) — 저장 없음 · 고침 = 화면 캐시 `수식고침` → [저장].
|
||||||
|
더한 기초 [연결 끊기](796-2e · `M02_Sangeun_Block`) `…/sangeun/block/unlink` · `/relink`(화면 캐시 문서 고침 ·
|
||||||
|
저장 없음 · 층 읽기) · 끊은 기초 수식 = 같은 equation 길 몸 `블록`(더한줄 차례).
|
||||||
|
|
||||||
저장 안 함 · 로그인 사용자 · 층 읽기 권한(`Router_Items._root`) · 화면은 최신 응답만.
|
저장 안 함 · 로그인 사용자 · 층 읽기 권한(`Router_Items._root`) · 화면은 최신 응답만.
|
||||||
몸 = `문서`(저장 전 화면 문서) 또는 `키`(그 층 저장본) + `입력값`(열린입력 · 시험 값) ·
|
몸 = `문서`(저장 전 화면 문서) 또는 `키`(그 층 저장본) + `입력값`(열린입력 · 시험 값) ·
|
||||||
@@ -26,6 +28,7 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
from common_util.common_util_auth import verify_session
|
from common_util.common_util_auth import verify_session
|
||||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Block as blk
|
||||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||||
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
||||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||||||
@@ -232,6 +235,7 @@ class EquationBody(BaseModel):
|
|||||||
문서: dict[str, Any]
|
문서: dict[str, Any]
|
||||||
inputs: dict[str, Any] | None = None
|
inputs: dict[str, Any] | None = None
|
||||||
고침: list[Any] | None = None
|
고침: list[Any] | None = None
|
||||||
|
블록: int | None = None # 끊은 더한 기초 차례(796-2e) — 없으면 바탕 줄
|
||||||
|
|
||||||
|
|
||||||
@router.post("/layers/{layer}/sangeun/equation")
|
@router.post("/layers/{layer}/sangeun/equation")
|
||||||
@@ -242,6 +246,8 @@ def sangeun_equation(
|
|||||||
session: Session = Depends(verify_session),
|
session: Session = Depends(verify_session),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""수식 탭 읽기(796-2b) — 사본(가져온 식) 줄 + 문서 `수식고침` → {ok, 수식 | reason}."""
|
"""수식 탭 읽기(796-2b) — 사본(가져온 식) 줄 + 문서 `수식고침` → {ok, 수식 | reason}."""
|
||||||
|
if body.블록 is not None:
|
||||||
|
return _call(_root(session, layer, owner), _sd(blk.read), body.문서, body.블록, body.inputs)
|
||||||
return _call(_root(session, layer, owner), _sd(eqm.read), body.문서, body.inputs)
|
return _call(_root(session, layer, owner), _sd(eqm.read), body.문서, body.inputs)
|
||||||
|
|
||||||
|
|
||||||
@@ -255,4 +261,27 @@ def sangeun_equation_preview(
|
|||||||
"""수식 고침 미리 셈(796-2b · 층 쓰기 · 저장 없음) — 고침 = `수식고침` 통째 →
|
"""수식 고침 미리 셈(796-2b · 층 쓰기 · 저장 없음) — 고침 = `수식고침` 통째 →
|
||||||
{되나, 까닭, 줄, 수식, 전, 후}."""
|
{되나, 까닭, 줄, 수식, 전, 후}."""
|
||||||
folder = _root(session, layer, owner, write=True)
|
folder = _root(session, layer, owner, write=True)
|
||||||
|
if body.블록 is not None:
|
||||||
|
args = (body.문서, body.블록, body.고침 or [], body.inputs)
|
||||||
|
return _call(folder, _sd(blk.preview), *args)
|
||||||
return _call(folder, _sd(eqm.preview), body.문서, body.고침 or [], body.inputs)
|
return _call(folder, _sd(eqm.preview), body.문서, body.고침 or [], body.inputs)
|
||||||
|
|
||||||
|
|
||||||
|
class BlockBody(BaseModel):
|
||||||
|
문서: dict[str, Any]
|
||||||
|
차례: int
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/layers/{layer}/sangeun/block/{how}")
|
||||||
|
def sangeun_block(
|
||||||
|
layer: Layer,
|
||||||
|
how: str,
|
||||||
|
body: BlockBody,
|
||||||
|
owner: str | None = None,
|
||||||
|
session: Session = Depends(verify_session),
|
||||||
|
) -> dict:
|
||||||
|
"""더한 기초 연결(796-2e) — unlink [연결 끊기] · relink [다시 잇기] · 답 = 고친 문서(화면 캐시 ·
|
||||||
|
[저장] 때 서버가 다시 셈) + 전 · 후 SK 금액."""
|
||||||
|
if how not in ("unlink", "relink"):
|
||||||
|
raise HTTPException(status_code=404, detail="unlink · relink")
|
||||||
|
return _call(_root(session, layer, owner), _sd(blk.change), body.문서, body.차례, how)
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
||||||
|
"""796-2e M02 단가산출 더한 기초 블록 [연결 끊기] · [다시 잇기] (임시 storage · M01 은 메모리 덮개).
|
||||||
|
|
||||||
|
① 연결 블록 = 원본(M01) 바뀜을 [재생성] 에서 따라감 ② [연결 끊기] = 금액 그대로 · 이 SK 안 자기 사본 ·
|
||||||
|
원본 바뀜 안 따라감([재생성] 도) ③ 끊은 기초 줄 고침 = 금액 바뀜(미리 셈 = 저장 뒤 서버 셈 · 틀린 고침 400 ·
|
||||||
|
M01 안 바뀜) ④ [다시 잇기] = 고친 것 버림 · 원본 따라감 ⑤ [단가 갱신] = 연결 · 끊은 둘 다 자원 단가 따라감
|
||||||
|
⑥ 꼴 검사(연결 기초에 고침 · 이미 끊음 · 차례 밖).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Block as blk
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||||||
|
from M02_MasterTemplete import M02_Sangeun_Equation_Full as full
|
||||||
|
from resources.tester.test_795_1_sk_copy import _calc, _changed, _doc, _fake, _live, _save, _sk
|
||||||
|
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
|
||||||
|
|
||||||
|
FORM = "GC000211" # 합판거푸집
|
||||||
|
BASIC = "GC001148" # 굴착기 시간당 — 호표 0 = 시간당 기계 × (1 / 작업량)
|
||||||
|
ADD = {"종류": "로직", "로직": BASIC, "수량": 2, "입력값": {}, "이름": "sub3임시 굴착기"}
|
||||||
|
ONLY = "LB000048" # 굴착기 기초에만 닿는 노임(거푸집엔 안 닿음)
|
||||||
|
DOUBLE = [{"할일": "식", "자리": "호표:0", "식": "3 / 작업량", "까닭": "sub3임시 시험"}]
|
||||||
|
|
||||||
|
|
||||||
|
def _sum(folder, doc):
|
||||||
|
return _calc(folder, doc)["기준"]["계"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def made(world):
|
||||||
|
me = world["personal"]
|
||||||
|
key = _sk(me, 바탕={"로직": FORM}, 더한줄=[copy.deepcopy(ADD)])
|
||||||
|
return me, key
|
||||||
|
|
||||||
|
|
||||||
|
def _cut(me, key):
|
||||||
|
"""끊어서 저장한 SK — (끊기 전 금액, 끊은 문서)."""
|
||||||
|
doc = _doc(me, key)
|
||||||
|
with store.at(me):
|
||||||
|
got = blk.change(doc, 0, "unlink")
|
||||||
|
assert got["전"]["계"] == got["후"]["계"] # 끊기 전후 같음
|
||||||
|
_save(me, key, got["문서"])
|
||||||
|
return got["전"]["계"], _doc(me, key)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unlink_keeps_amount_and_freezes_rows(made):
|
||||||
|
"""② 끊기 = 금액 그대로 · 닫힌 묶음(하위 로직 포함) 이 SK 안에 · 저장 뒤도 같음 · 수식 탭 표지."""
|
||||||
|
me, key = made
|
||||||
|
was = _sum(me, _doc(me, key))
|
||||||
|
plain = _calc(me, _doc(me, key))
|
||||||
|
assert plain["기준"]["계"] == was
|
||||||
|
row_before = copy.deepcopy(_live().logic(BASIC))
|
||||||
|
price, doc = _cut(me, key)
|
||||||
|
assert price == was and _sum(me, doc) == was
|
||||||
|
cut = doc["더한줄"][0]["끊음"]
|
||||||
|
keys = {r["키"] for r in cut["로직"]}
|
||||||
|
assert BASIC in keys and "GC000267" in keys and "GC001147" in keys # 하위 로직까지
|
||||||
|
assert not any(k[:2] in skc.RES for k in keys) # 단가는 SK 사본 한 곳
|
||||||
|
assert _live().logic(BASIC) == row_before # M01 안 바뀜
|
||||||
|
with store.at(me):
|
||||||
|
eq = full.attach({"줄": []}, doc, skc.over(doc, skc.whole_()), {}, None, _calc(me, doc))
|
||||||
|
block = next(r for r in eq["소계"]["줄"] if r.get("블록") == 0)
|
||||||
|
assert block["끊음"] is True and block["나무"] and block["상태"] == "더함"
|
||||||
|
assert not block.get("덮개") # 끊기만으로 「세부」 고침 표지 없음
|
||||||
|
|
||||||
|
|
||||||
|
def test_connected_follows_original_on_regen_cut_does_not(made, monkeypatch):
|
||||||
|
"""①·② 원본 바뀜 — 연결 = [재생성] 에서 따라감 · 끊음 = [재생성] 도 안 따라감."""
|
||||||
|
me, key = made
|
||||||
|
was = _sum(me, _doc(me, key))
|
||||||
|
_fake(monkeypatch, [_changed(_live().logic(BASIC))]) # 호표 0 수량 × 2 (원본 바뀜)
|
||||||
|
assert _sum(me, _doc(me, key)) == was # 누르기 전 = 그대로(자동 갱신 없음)
|
||||||
|
with store.at(me):
|
||||||
|
got = skc.preview(_doc(me, key), skc.REGEN)
|
||||||
|
assert got["후"]["계"] > was and "끊은기초" not in got # 연결 = 원본 따라감
|
||||||
|
monkeypatch.undo()
|
||||||
|
price, doc = _cut(me, key)
|
||||||
|
assert price == was
|
||||||
|
_fake(monkeypatch, [_changed(_live().logic(BASIC))])
|
||||||
|
with store.at(me):
|
||||||
|
got = skc.preview(doc, skc.REGEN)
|
||||||
|
assert got["전"]["계"] == got["후"]["계"] == was # 끊음 = 안 따라감
|
||||||
|
assert got["끊은기초"] == ["sub3임시 굴착기"] # 보고 한 줄
|
||||||
|
_save(me, key, got["문서"])
|
||||||
|
assert _sum(me, _doc(me, key)) == was
|
||||||
|
|
||||||
|
|
||||||
|
def test_price_refresh_follows_in_both(made, monkeypatch):
|
||||||
|
"""⑤ [단가 갱신] — 연결 · 끊은 기초 모두 자원 단가는 따라감(요구 656-1) · 끊은 기초 줄은 그대로."""
|
||||||
|
me, key = made
|
||||||
|
was = _sum(me, _doc(me, key))
|
||||||
|
row = copy.deepcopy(_live().get(ONLY))
|
||||||
|
row["값"] = row["값"] * 2
|
||||||
|
_fake(monkeypatch, [row])
|
||||||
|
with store.at(me):
|
||||||
|
fresh = skc.preview(_doc(me, key), skc.PRICE)
|
||||||
|
assert fresh["후"]["계"] > was and [x["키"] for x in fresh["바뀐자원"]] == [ONLY]
|
||||||
|
monkeypatch.undo()
|
||||||
|
_, doc = _cut(me, key)
|
||||||
|
_fake(monkeypatch, [row])
|
||||||
|
with store.at(me):
|
||||||
|
cut = skc.preview(doc, skc.PRICE)
|
||||||
|
assert cut["후"]["계"] == fresh["후"]["계"] and cut["끊은기초"] == ["sub3임시 굴착기"]
|
||||||
|
_save(me, key, cut["문서"])
|
||||||
|
assert _sum(me, _doc(me, key)) == fresh["후"]["계"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_edit_cut_block_changes_amount_and_saves(made):
|
||||||
|
"""③ 끊은 기초 줄 고침 — 미리 셈 = 금액 바뀜 · 저장 뒤 서버 셈 같음 · 틀린 고침 400 · M01 안 바뀜."""
|
||||||
|
me, key = made
|
||||||
|
price, doc = _cut(me, key)
|
||||||
|
row_before = copy.deepcopy(_live().logic(BASIC))
|
||||||
|
with store.at(me):
|
||||||
|
got = blk.preview(doc, 0, DOUBLE)
|
||||||
|
read = blk.read(doc, 0)
|
||||||
|
assert read["ok"] and got["되나"], got
|
||||||
|
assert got["금액전"]["계"] == price and got["금액후"]["계"] > price
|
||||||
|
assert got["수식"]["합계"]["계"] > got["전"]["계"] # 기초 1 단위 시간당 값
|
||||||
|
item = {**doc["더한줄"][0], blk.EDIT: DOUBLE}
|
||||||
|
_save(me, key, {**doc, "더한줄": [item]})
|
||||||
|
saved = _doc(me, key)
|
||||||
|
assert saved["더한줄"][0][blk.EDIT][0]["까닭"] == "sub3임시 시험"
|
||||||
|
assert _sum(me, saved) == got["금액후"]["계"] # 서버 셈 = 미리 셈
|
||||||
|
assert _live().logic(BASIC) == row_before
|
||||||
|
with store.at(me):
|
||||||
|
eq = full.attach(
|
||||||
|
{"줄": []}, saved, skc.over(saved, skc.whole_()), {}, None, _calc(me, saved)
|
||||||
|
)
|
||||||
|
block = next(r for r in eq["소계"]["줄"] if r.get("블록") == 0)
|
||||||
|
assert block["나무"]["합계"]["계"] == got["수식"]["합계"]["계"] # 접힘 블록 = 고친 수식
|
||||||
|
bad = [{"할일": "식", "자리": "호표:0", "식": "없는값 / 작업량"}]
|
||||||
|
with store.at(me):
|
||||||
|
assert not blk.preview(saved, 0, bad)["되나"]
|
||||||
|
with pytest.raises(store.StoreError):
|
||||||
|
_save(me, key, {**saved, "더한줄": [{**saved["더한줄"][0], blk.EDIT: bad}]})
|
||||||
|
assert _sum(me, _doc(me, key)) == got["금액후"]["계"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_relink_drops_edits_and_follows_original(made, monkeypatch):
|
||||||
|
"""④ [다시 잇기] — 고친 것 버림 · 원본 따라감(저장 뒤 표지 없음) · 금액 = 새로 찍은 SK."""
|
||||||
|
me, key = made
|
||||||
|
was = _sum(me, _doc(me, key))
|
||||||
|
_, doc = _cut(me, key)
|
||||||
|
item = {**doc["더한줄"][0], blk.EDIT: DOUBLE}
|
||||||
|
_save(me, key, {**doc, "더한줄": [item]})
|
||||||
|
edited = _doc(me, key)
|
||||||
|
assert _sum(me, edited) > was
|
||||||
|
_fake(monkeypatch, [_changed(_live().logic(BASIC))]) # 원본 바뀜
|
||||||
|
fresh = _sum(me, _doc(me, _sk(me, 바탕={"로직": FORM}, 더한줄=[copy.deepcopy(ADD)])))
|
||||||
|
assert fresh > was
|
||||||
|
with store.at(me):
|
||||||
|
got = blk.change(edited, 0, "relink")
|
||||||
|
one = got["문서"]["더한줄"][0]
|
||||||
|
assert "끊음" not in one and blk.EDIT not in one and one[blk.REJOIN] is True
|
||||||
|
assert got["전"]["계"] > was and got["후"]["계"] == fresh # 고침 버림 + 원본 따라감
|
||||||
|
_save(me, key, got["문서"])
|
||||||
|
saved = _doc(me, key)
|
||||||
|
assert blk.REJOIN not in saved["더한줄"][0] and "끊음" not in saved["더한줄"][0]
|
||||||
|
assert _sum(me, saved) == fresh
|
||||||
|
|
||||||
|
|
||||||
|
def test_shape_checks(made):
|
||||||
|
"""⑥ 연결 기초에 고침 · 이미 끊음 · 차례 밖 · 연결 기초 다시 잇기 · 끊은 기초 줄 모양."""
|
||||||
|
me, key = made
|
||||||
|
doc = _doc(me, key)
|
||||||
|
with store.at(me):
|
||||||
|
with pytest.raises(sd.DocError, match="연결을 끊은 기초만"):
|
||||||
|
sd.clean({**doc, "더한줄": [{**ADD, blk.EDIT: DOUBLE}]})
|
||||||
|
with pytest.raises(sd.DocError, match="없음"):
|
||||||
|
blk.unlink(doc, 3)
|
||||||
|
with pytest.raises(sd.DocError, match="연결된 기초"):
|
||||||
|
blk.relink(doc, 0)
|
||||||
|
with pytest.raises(sd.DocError, match="연결된 기초"):
|
||||||
|
blk.read(doc, 0)
|
||||||
|
cut = blk.unlink(doc, 0)
|
||||||
|
with pytest.raises(sd.DocError, match="이미 연결을 끊은"):
|
||||||
|
blk.unlink(cut, 0)
|
||||||
|
broken = {**cut["더한줄"][0], "끊음": {"로직": [{"키": "GC999999"}]}}
|
||||||
|
with pytest.raises(sd.DocError, match="줄 없음"):
|
||||||
|
sd.clean({**cut, "더한줄": [broken]}, None, skc.whole_())
|
||||||
Reference in New Issue
Block a user