Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_1' into sub_laptop_3

This commit is contained in:
2026-11-08 21:04:41 +09:00
8 changed files with 604 additions and 25 deletions
+4 -2
View File
@@ -40,7 +40,9 @@ def test_화면_서고_상세는_일위대가표만():
_src("M02_MasterTemplete_Labor_Detail.ts"),
)
assert "return mountLaborTable(host, doc, opt);" in detail # 806-1 — 조합도 같은 부품
assert "calc = settle(calc, cur, combo?.given() ?? {});" in table # 짝 셈 바로(742-1 수량 = 문서)
assert (
"calc = settle(calc, cur, combo?.given() ?? {});" in table
) # 짝 셈 바로(742-1 수량 = 문서)
assert 'const out: Record<string, unknown> = { ...cur, 종류: "사전조합" };' in table
for label in ('tc("Up")', 'tc("Down")', 't("Alias")', 'tc("Remove")'):
assert label in table # 우클릭 메뉴
@@ -55,5 +57,5 @@ def test_화면_줄_모달은_SK_상세_줄_모드_적용은_덮개와_옵션():
detail = _src("M02_MasterTemplete_Sangeun_Detail.ts")
assert "mountSangeunDetail(box, doc," in sk and "getValues: () => ({ ...values })" in detail
assert "if (got.patch) row.패치 = got.patch;" in modal and "setValues(got.values);" in modal
assert 'createCheckbox(t("AlsoSk"), { checked: false })' in modal # 기본 꺼짐
assert "AlsoSk" not in modal # 808-1 [단가산출로도 저장] 걷음 · 811-1 M01 기초 = SK 사본 참조
assert "openRowSangeun" not in modal # 717-3 두 보기 갈음
+10 -11
View File
@@ -138,7 +138,7 @@ def test_SC_에_SK_패치_겹침_E1_원본_고쳐도_그대로(world):
before = _sc_value(me, old)
host = _make(me, "precombo", 이름="밖", 담은로직=lines)
doc = _read(me, "precombo", host)
assert doc["담은로직"][0]["로직"] == PCT and doc["담은로직"][0]["본뜬"]["키"] == sk
assert doc["담은로직"][0]["참조"] == {**MINE, "키": sk} # 811-1 SK 줄 = 참조 한 행(안 펼침)
assert doc["담은로직"][0]["패치"]["덮개"] == {"labor:LB000036#0": {"qty": 0.5}}
assert doc["담은로직"][1]["참조"]["층"] == "사본" # 하위 호표(SC 안 SC) = 사본 블록
assert doc["담은로직"][1]["본뜬"]["키"] == inner
@@ -148,8 +148,8 @@ def test_SC_에_SK_패치_겹침_E1_원본_고쳐도_그대로(world):
store.write(
"sangeun", sk, now["판"], {**now["문서"], "덮개": {"labor:LB000002#0": {"qty": 2}}}
)
assert _sc_value(me, old) > before * 3 # 옛 참조 = 따라감(129,073 → 481,406 꼴)
assert _sc_value(me, host) == pytest.approx(before, rel=1e-9) # 펼친 것 = 그대로
assert _sc_value(me, old) > before * 3 # 참조 = 따라감(129,073 → 481,406 꼴)
assert _sc_value(me, host) == pytest.approx(_sc_value(me, old), rel=1e-9) # 811-1 = SK 따라감
def test_SC_에_SC_사본_블록_실무수량_겹침_원본_지워도(world):
@@ -322,18 +322,19 @@ def test_옮기기_함수_전후_같음_사본_블록(world):
sk: _sk_total(me, _read(me, "sangeun", sk))
}
look = {r["키"]: r for r in flat.migrate(False)}
assert all(look[k]["같음"] and look[k]["펼침"] and not look[k]["씀"] for k in (sk, sc, dup))
assert all(look[k]["같음"] and not look[k]["씀"] for k in (sk, sc, dup))
assert look[sc]["남은참조"] == [y] # 811-1 SC 안 SK 줄 = 참조 한 행 그대로
rows = {r["키"]: r for r in flat.migrate(True)}
assert all(rows[k]["씀"] for k in (sk, sc, dup))
assert not refs.refs_in(json.loads((me / "sangeun" / f"{sk}.json").read_bytes()))
for key in (sc, dup): # 하위 호표 = 사본 블록만 남음(원본 연결 없음)
for key, keep in ((sc, [("personal", y)]), (dup, [])): # 하위 호표 = 사본 블록
left = refs.refs_in(json.loads((me / "precombo" / f"{key}.json").read_bytes()))
assert [(r["층"], r["키"]) for r in left] == [("사본", inner)]
assert [(r["층"], r["키"]) for r in left] == [*keep, ("사본", inner)]
assert _sk_total(me, _read(me, "sangeun", sk)) == pytest.approx(before[sk], rel=1e-9)
assert _sc_value(me, sc) == pytest.approx(before[sc], rel=1e-9)
assert _sc_value(me, dup) == pytest.approx(before[dup], rel=1e-9)
again = flat.migrate(True) # 두 번째 = 바꿀 것 없음
assert all(r["펼침"] and not r["씀"] for r in again)
assert all(not r["씀"] for r in again)
def test_733_지문_옛_참조_펼친_것_같음(world):
@@ -341,7 +342,7 @@ def test_733_지문_옛_참조_펼친_것_같음(world):
y = _y(me)
old = _raw(me, "precombo", 이름="옛", 담은로직=[{"참조": {**MINE, "키": y}}, {"로직": GF}])
new = _make(me, "precombo", 이름="새", 담은로직=[{"참조": {**MINE, "키": y}}, {"로직": GF}])
assert not refs.refs_in(_read(me, "precombo", new))
assert refs.refs_in(_read(me, "precombo", new)) == [{**MINE, "키": y}] # 811-1 참조 한 행
fingers = {k: same.finger("precombo", _read(me, "precombo", k)) for k in (old, new)}
assert fingers[old] == fingers[new]
@@ -387,7 +388,5 @@ def test_747_SK_재료_직접계산_일위대가에_넣어도_같은_금액(worl
before = _sc_value(me, old)
host = _make(me, "precombo", 이름="새", 담은로직=lines)
line = _read(me, "precombo", host)["담은로직"][0]
assert line["자재표시"] and all(
v["별도계상"] == "직접계산" for v in line["자재표시"].values()
)
assert line["참조"]["키"] == sk # 811-1 참조 한 행 = SK 재료 직접계산 그대로
assert _sc_value(me, host) == pytest.approx(before, rel=1e-9)
+201
View File
@@ -0,0 +1,201 @@
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""811-1 일위대가 단가산출 줄 = SK 참조 한 행(요구 707-1 · 681 · 698) — 임시 storage · M01 읽기만.
- 저장 · 읽기 = 안 펼침(SK 참조 그대로) · 셈 = SK 1 단위 × 수량 · 모달 저장 = 그 SK 문서 → 일위대가 금액 따라감
- [+ 줄] M01 기초 = 그 층 SK 사본(`from-logic` · 같은 내용 = 409 물음 · 씀 · 새로) · 일반 사용자 회사 층 403
- 다른 층 SK = 이 층 사본 뒤 참조(화면 `Labor_SkPick` · 서버 서고 복사 길)
- 옮기기 = 로직 줄 → SK + 참조 한 행 · 면적 칸 잇기 옮김 · 전후 금액 같음 · 보기 = 임시 사본 · 실제 저장본 전수
"""
import json
import re
from decimal import Decimal
from pathlib import Path
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from common_util.common_util_auth import verify_session
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_Labor_SkRow as skrow
from M02_MasterTemplete import M02_Library_Flat as flat
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_Sangeun_Calc as calc
from M02_MasterTemplete import M02_Sangeun_Doc as sd
from M02_MasterTemplete import M02_Sangeun_Router as sk_router
from M02_MasterTemplete import M02_Template_Layers as layers
from M02_MasterTemplete.M02_Labor_Expand import Expand
from resources.tester.test_740_1_library_flat import USER, _make, _read, _whole
from resources.tester.test_m02_labor_ref import MINE, STONE, TEN, world # noqa: F401
ROOT = Path(__file__).resolve().parents[2]
M02 = ROOT / "M02_MasterTemplete"
FORM = "GC000211" # 합판거푸집 — 재료 줄 있음(일위대가 기본 별도계상)
def _src(name: str) -> str:
return (M02 / name).read_text("utf-8")
def _value(folder, key, given=None):
with store.at(folder):
got = Expand(_whole(), refs.read_ref, given or {})({**MINE, "키": key})
return got["기준"]
def test_참조_한_행_저장_읽기_안_펼침_셈은_SK_곱_수량(world):
me = world["personal"]
sk = _make(me, "sangeun", 이름="sub1임시 SK", 바탕={"로직": TEN})
sc = _make(me, "precombo", 이름="sub1임시 SC", 담은로직=[{"참조": {**MINE, "키": sk}, "수량": {"손": 3}}]) # fmt: skip
doc = _read(me, "precombo", sc)
assert doc["담은로직"][0]["참조"] == {**MINE, "키": sk} and "로직" not in doc["담은로직"][0]
assert flat.flat_doc("precombo", doc)["담은로직"][0]["참조"]["키"] == sk # 읽기 길도 그대로
one, got = _value(me, sk), _value(me, sc)
want = labor.mcut.sums([labor.mcut.row({k: v * 3 for k, v in one.items()})])
assert got["계"] == want["계"] and got["계"] > 0
def test_모달_저장_SK_문서_뒤_일위대가_따라감(world):
me = world["personal"]
sk = _make(me, "sangeun", 이름="sub1임시 SK", 바탕={"로직": TEN})
sc = _make(me, "precombo", 이름="sub1임시 SC", 담은로직=[{"참조": {**MINE, "키": sk}, "수량": {"손": 1}}]) # fmt: skip
before = _value(me, sc)["계"]
with store.at(me): # 모달 [저장] = 그 SK 문서 쓰기(`saveTemplate` sangeun)
now = store.read("sangeun", sk)
cid = sd.line_ids(_whole().logic(TEN))[0]
doc = {**now["문서"], "덮개": {cid: {"배율": 2}}}
store.write("sangeun", sk, now["판"], calc.clean_doc(doc, refs.here()))
assert _value(me, sc)["계"] != before # 일위대가 금액이 SK 를 따라감
assert _read(me, "precombo", sc)["담은로직"][0]["참조"]["키"] == sk # 일위대가 문서는 그대로
def test_from_logic_층_SK_사본_같은것_물음_권한(world):
app = FastAPI()
app.include_router(sk_router.router)
app.dependency_overrides[verify_session] = lambda: USER
client = TestClient(app)
url = "/api/m02/layers/{}/sangeun/from-logic"
res = client.post(url.format("personal"), json={"로직": TEN})
assert res.status_code == 200, res.text
key, ref = res.json()["이름"], res.json()["참조"]
assert ref == {**MINE, "키": key}
doc = _read(world["personal"], "sangeun", key)
assert doc["바탕"] == {"로직": TEN} and doc["단위"] == "㎡" # 원/10㎡ → ㎡(줄 ÷ 배와 같은 값)
again = client.post(url.format("personal"), json={"로직": TEN})
assert again.status_code == 409 # 같은 내용 = 물음(733-2)
use = client.post(url.format("personal"), json={"로직": TEN, "씀": key})
assert use.json()["이름"] == key
new = client.post(url.format("personal"), json={"로직": TEN, "새로": True})
assert new.json()["이름"] != key
assert client.post(url.format("personal"), json={"로직": "GF999999"}).status_code == 400
assert client.post(url.format("company"), json={"로직": TEN}).status_code == 403
def test_M01_기초_SK_값_로직_줄과_같음_재료_별도계상(world):
"""새 SK 1 단위 = 옛 로직 줄 ÷ 배 · 재료 빈 칸 = 일위대가 기본(별도계상)을 적어 둠."""
me = world["personal"]
for code in (TEN, STONE, FORM):
with store.at(me):
got = skrow.from_logic(code, new=True)
line = labor.clean({"담은로직": [{"로직": code, "수량": {"손": 2}}]})
ref = labor.clean({"담은로직": [{"참조": got["참조"], "수량": {"손": 2}}]})
a, b = labor.calc(line, {}), labor.calc(ref, {})
assert a["반영합"] == b["반영합"], code
doc = _read(me, "sangeun", got["이름"])
assert doc["자재표시"] and all(v["별도계상"] == "별도계상" for v in doc["자재표시"].values())
def _labor_world(me):
"""개인 층 조합 일위대가(수량 빈 줄 = 면적 칸) + 면적 문서 + SC(로직 줄 · 손 수량)."""
lines = [{"로직": TEN, "수량": None}, {"로직": STONE, "수량": {"손": 2}}]
pill = combo_store.combo_pills({"담은로직": lines})["물량"][0]["키"]
with store.at(me):
doc = labor.clean({"담은로직": lines})
(me / "labor").mkdir(parents=True, exist_ok=True)
(me / "area").mkdir(parents=True, exist_ok=True)
(me / "labor" / "c811.json").write_text(json.dumps({**doc, "열": "c811", "단위": "㎡"}, ensure_ascii=False), "utf-8") # fmt: skip
area = {"시트": [{"id": "s1", "칸": {"A1": {"값": "4"}}, "연결": [{"키": pill, "칸": "A1"}]}]}
(me / "area" / "c811.json").write_text(json.dumps(area, ensure_ascii=False), "utf-8")
sc = _make(me, "precombo", 이름="sub1임시 SC", 담은로직=[{"로직": TEN, "수량": {"손": 3}}])
return pill, sc
def test_옮기기_로직_줄_SK_참조_면적_잇기_전후_같음(world):
me = world["personal"]
pill, sc = _labor_world(me)
with store.at(me):
before = labor.calc(json.loads((me / "labor" / "c811.json").read_bytes()))["반영합"]
sc_before = _value(me, sc)
rows = {r["키"]: r for r in skrow.run_here()}
assert rows["c811"]["같음"] and rows[sc]["같음"] and rows["c811"]["알약"] == 1
doc = json.loads((me / "labor" / "c811.json").read_bytes())
assert all("참조" in x and "로직" not in x for x in doc["담은로직"])
assert [x["수량"] for x in doc["담은로직"]] == [None, {"손": 2}] # 수량 · 별칭 그대로
area = json.loads((me / "area" / "c811.json").read_bytes())
keys = [x["키"] for x in area["시트"][0]["연결"]]
assert keys == [refs.ref_id(doc["담은로직"][0]["참조"])] and pill not in keys
with store.at(me):
assert labor.calc(doc)["반영합"] == before # 원 단위 같음(면적 칸 4)
assert _value(me, sc) == sc_before
assert refs.refs_in(_read(me, "precombo", sc))[0]["키"].startswith("SK-")
made = {k for r in rows.values() for k in r["새SK"]}
assert len(made) == 3 # SC TEN · 조합 TEN · STONE — 사본(값 묶음)이 달라 공유 안 함
assert skrow.run_here() == [] # 두 번째 = 옮길 로직 줄 없음
def test_옮기기_다르면_되돌림(world, monkeypatch):
me = world["personal"]
_labor_world(me)
raw = (me / "labor" / "c811.json").read_bytes()
real = skrow.money
monkeypatch.setattr(
skrow, "money", lambda k, key, doc: {"다름": id(doc)} if k == "labor" else real(k, key, doc)
)
rows = {r["키"]: r for r in skrow.run_here()}
assert not rows["c811"]["같음"] and rows["c811"]["공유없음"]
assert (me / "labor" / "c811.json").read_bytes() == raw # 원래대로
left = {p.stem for p in (me / "sangeun").glob("SK-*.json")}
sc_made = set(rows[next(k for k in rows if k != "c811")]["새SK"])
assert left == sc_made # 되돌린 문서가 만든 SK 는 지움
def test_보기_임시_사본_공유_storage_안_씀(world, tmp_path):
me = world["personal"]
_labor_world(me)
raw = (me / "labor" / "c811.json").read_bytes()
rows = skrow.migrate(False, work=tmp_path)
assert rows and all(r["같음"] for r in rows)
assert (me / "labor" / "c811.json").read_bytes() == raw # 보기 = 진짜 층 안 고침
assert not (me / "sangeun").exists() or not list((me / "sangeun").glob("SK-*.json"))
skrow.to_xlsx(rows, tmp_path / "목록.xlsx")
assert (tmp_path / "목록.xlsx").stat().st_size > 0
@pytest.mark.skipif(
not any(layers.storage_root().glob("*/*/templates/labor/*.json")), reason="저장본 없음"
)
def test_실제_저장본_전수_보기_같음(tmp_path):
"""공유 storage 층 문서 전부 — 임시 사본에서 옮겨 봄(쓰지 않음) · 줄마다 금액 같음."""
rows = skrow.migrate(False, work=tmp_path)
assert rows and all(r["같음"] for r in rows), [r["키"] for r in rows if not r["같음"]]
def test_화면_줄_넣기_SK_사본_참조_부품():
table = _src("M02_MasterTemplete_Labor_Table.ts")
pick = _src("M02_MasterTemplete_Labor_SkPick.ts")
assert 'opt.layer !== "project" && !opt.calc ? { layer: opt.layer' in table # 프로젝트 = 옛 길
assert "return void skFromLogic(own, x.키).then(" in table # M01 기초 = SK 사본
assert "own && isSkRef(ref)" in table and "void ownSk(own, ref)" in table # 다른 층 = 사본
assert "if (isSkRef(row.참조) && !opt.calc)" in table # 줄 누름 = SK 수식 모달
assert "/sangeun/from-logic" in pick and "본뜬:" in pick and "withSameAsk(" in pick
for text in (table, pick):
assert len(text.splitlines()) <= 700
assert not re.search(r"""\bel\(\s*["'](button|input|select)["']""", text)
assert len((M02 / "M02_Labor_SkRow.py").read_text("utf-8").splitlines()) <= 700
def test_값_한_벌_소수():
assert skrow._unit({"기준": {"계": Decimal("1289832")}, "배": 100}) == "12898.32"