- 새 M02_Library_Flat: SK 에 SK = 더한줄 로직 한 줄에 원본 몸 통째(덮개 · 뺀줄 · 고름 · 더한줄 · % 덧줄까지 같은 금액) · SC 에 SK = 로직 줄 + 패치 · SK 에 SC = 줄마다 더한줄(수량 = 저장 수량.손 › 실무수량 › 1) · 바탕 SK · 패치 SK 참조 = 덮개 겹침 · 줄 본뜬 {층, 주인, 키, 이름, 때}
- 저장 · 읽기 · 층 복사 때 서버가 펼침(못 펼친 줄 = 옛 참조 그대로) · SC 안 SC(하위 호표)는 저장 · 읽기에서 참조 그대로(엑셀 하위 호표 끝수 · B09 하위 대가) · 찾아 넣기 길만 펴 넣음
- 길: POST {kind}-flat(넣을 줄 · 못 펼침 400) · POST {kind}-reset(미리 보기 걷힐 수 · 씀)
- 초기화 = 덮개 · 뺀줄 · 더한줄 단가 걷음(안쪽 넣은 몸까지) · 항목 · 수량 · 입력값 · 고름 남김
- 옮기기 = migrate(apply) · 전후 1 단위 금액 같을 때만 씀 · 공유 storage 는 브레인
- 둘째 기초 세부 셈 = 넣은 SK 몸이면 이름 없는 SK 로(Sangeun_Calc.sub_run)
- B09: SC 사본 덮개 줄 = 굳힌 줄(굳힌 {키, 줄 …}) → 사본 셈 · 대가 블록이 덮개를 읽음(옛 산근 길은 덮개 무시 · 실측 6,451 대 M02 210,866)
- 733 지문 = 옛 참조 줄 펼친 꼴로 대조
- 낡은 시험 고침(까닭 = 단가산출이 낀 참조가 이제 펼쳐 담김): 715 store(바탕 · 사전조합 SK · 층 복사) · 717-2a 목록 바탕 · 717-4 SC 안 SK · 719-1 원본판(원본 고쳐도 그대로) · 719-2 속 일위대가(빠진 줄 없음 · 쓰지 않는 _calc_given 지움) · 725-1 SK 안 SC · 729 SK 줄 본뜬 · 733-3 쓰는 곳 = 구조물 조합(SK 안 SK 참조 없음) · 572 지문 대조 대상
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
215 lines
9.3 KiB
Python
215 lines
9.3 KiB
Python
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
||
"""서고 [중복 찾기] · 합치기 · 되돌림(PLAN 733-3 · 요구서_572 3장 4) — 임시 storage.
|
||
|
||
묶음 = 이 층 + 위 층 같은 지문 · 합치기 = 쓰는 곳 참조 → 남김(금액 같음) · 지운 문서 `_합친것/` ·
|
||
되돌림 = 문서 · 참조 되살림 · 지문 · 판 다르면 409 · 권한 밖 · 같은 참조 두 번 = 그 문서 남김 ·
|
||
쓰는 곳 = 구조물 조합(740-1 — 단가산출 · 일위대가끼리는 펼쳐 담겨 참조 없음) ·
|
||
위 층과 같은 아래 층 문서 = 남김 = 위 층 키 · 일반 사용자 회사 층 = 403.
|
||
"""
|
||
|
||
import pytest
|
||
from fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from common_util.common_util_auth import verify_session
|
||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||
from M02_MasterTemplete import M02_Library_Same_Router as same_router
|
||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||
from resources.tester.test_m02_labor_ref import COMPANY, MINE, world # noqa: F401
|
||
|
||
PCT = "GC001109" # 원/㎡ · 인력 둘 + 「노무비 * 3 / 100」
|
||
USER = {"user_id": 42, "company_id": 7, "role": "USER", "is_master": False, "name": "갑"}
|
||
ADMIN = {**USER, "role": "ADMIN", "name": "관리"}
|
||
URL = "/api/m02/layers/{}/sangeun-same"
|
||
|
||
|
||
@pytest.fixture()
|
||
def api(world):
|
||
app = FastAPI()
|
||
app.include_router(same_router.router)
|
||
who = dict(USER)
|
||
app.dependency_overrides[verify_session] = lambda: who
|
||
got = TestClient(app)
|
||
got.who = who
|
||
return got
|
||
|
||
|
||
def _make(folder, **doc):
|
||
with store.at(folder):
|
||
return refs.create("sangeun", {"단위": "㎡", "더한줄": [], **doc})["이름"]
|
||
|
||
|
||
def _read(folder, key):
|
||
with store.at(folder):
|
||
return store.read("sangeun", key)["문서"]
|
||
|
||
|
||
def _total(folder, key):
|
||
with store.at(folder):
|
||
return calc.calc_doc(store.read("sangeun", key)["문서"])["기준"]["계"]
|
||
|
||
|
||
def _top(ref_keys, at=MINE):
|
||
return [{"참조": {**at, "키": k}, "수량": 2} for k in ref_keys] + [
|
||
{"고정": {"이름": "운반", "단위": "식", "단가": {"경비": 5000}}, "수량": 1}
|
||
]
|
||
|
||
|
||
def _user(folder, ref_keys, at=MINE, col="x1"):
|
||
"""쓰는 곳 = 구조물 조합(740-1 — 단가산출 · 일위대가끼리는 펼쳐 담겨 참조 없음)."""
|
||
lines = [{**x, "수량": {"손": x["수량"]}} for x in _top(ref_keys, at)]
|
||
with store.at(folder):
|
||
store.write("labor", col, "", {"열": col, "담은로직": lines, "자재": []})
|
||
return col
|
||
|
||
|
||
def _lines(folder, col):
|
||
with store.at(folder):
|
||
return store.read("labor", col)["문서"]["담은로직"]
|
||
|
||
|
||
def _sum(folder, col):
|
||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||
|
||
with store.at(folder):
|
||
doc = labor.clean({**store.read("labor", col)["문서"], "열": col})
|
||
return float(labor.calc(doc, {})["반영합"]["계"])
|
||
|
||
|
||
def _merge(api, keep, gone, 판, layer="personal"):
|
||
body = {"남김": keep, "지움": gone, "판": 판}
|
||
return api.post(URL.format(layer) + "/merge", json=body)
|
||
|
||
|
||
def test_권한_밖이면_안_지움(api, world, monkeypatch):
|
||
me = world["personal"]
|
||
a = _make(me, 이름="가", 바탕={"로직": PCT})
|
||
c = _make(me, 이름="다", 바탕={"로직": PCT})
|
||
top = _user(me, [c])
|
||
monkeypatch.setattr(same_router, "_can_write", lambda session, at: False)
|
||
판 = api.get(URL.format("personal")).json()["판"]
|
||
res = _merge(api, {**MINE, "키": a}, [c], 판)
|
||
left = res.json()["남김"]
|
||
assert res.status_code == 200 and left[0]["키"] == c and "권한 밖" in left[0]["쓰는곳"][0]
|
||
assert res.json()["합침"] == [] and (me / "sangeun" / f"{c}.json").is_file()
|
||
assert _lines(me, top)[0]["참조"]["키"] == c
|
||
|
||
|
||
def test_합치기_참조_옮김_금액_같음_되돌림(api, world):
|
||
me = world["personal"]
|
||
a = _make(me, 이름="밑", 바탕={"로직": PCT})
|
||
b = _make(me, 이름="다른 이름 같은 내용", 바탕={"로직": PCT})
|
||
_make(me, 이름="다른 내용", 바탕={"로직": PCT}, 더한줄=_top([])[-1:])
|
||
top = _user(me, [b])
|
||
before = _sum(me, top)
|
||
got = api.get(URL.format("personal"))
|
||
assert got.status_code == 200, got.text
|
||
view = got.json()
|
||
assert len(view["묶음"]) == 1
|
||
rows = view["묶음"][0]["문서"]
|
||
assert [r["키"] for r in rows] == [a, b] and view["묶음"][0]["꼬리표"] == "내용 같음"
|
||
assert [r["쓰는곳수"] for r in rows] == [0, 1] and not any(r["위층"] for r in rows)
|
||
|
||
res = _merge(api, {**MINE, "키": a}, [b], view["판"])
|
||
assert res.status_code == 200, res.text
|
||
out = res.json()
|
||
assert out["합침"] == [{"키": b, "옮긴곳수": 1}] and out["남김"] == []
|
||
assert out["묶음"] == [] and [g["키"] for g in out["합친것"]] == [b]
|
||
assert not (me / "sangeun" / f"{b}.json").exists()
|
||
assert (me / same_router.GONE / f"{b}.json").is_file()
|
||
assert _lines(me, top)[0]["참조"]["키"] == a
|
||
assert _sum(me, top) == pytest.approx(before)
|
||
|
||
back = api.post(URL.format("personal") + "/undo", json={"키": b})
|
||
assert back.status_code == 200, back.text
|
||
assert back.json()["되돌린곳수"] == 1 and back.json()["합친것"] == []
|
||
assert (me / "sangeun" / f"{b}.json").is_file()
|
||
assert not (me / same_router.GONE / f"{b}.json").exists()
|
||
assert _lines(me, top)[0]["참조"]["키"] == b
|
||
assert _sum(me, top) == pytest.approx(before)
|
||
assert api.post(URL.format("personal") + "/undo", json={"키": b}).status_code == 404
|
||
|
||
|
||
def test_지문_판_다르면_409(api, world):
|
||
me = world["personal"]
|
||
a = _make(me, 이름="가", 바탕={"로직": PCT})
|
||
b = _make(me, 이름="나", 바탕={"로직": PCT})
|
||
판 = api.get(URL.format("personal")).json()["판"]
|
||
assert _merge(api, {**MINE, "키": a}, [b], "옛판").status_code == 409
|
||
with store.at(me): # 그새 b 내용이 바뀜
|
||
doc = store.read("sangeun", b)
|
||
store.write("sangeun", b, doc["판"], {**doc["문서"], "더한줄": _top([])[-1:]})
|
||
res = _merge(api, {**MINE, "키": a}, [b], 판)
|
||
assert res.status_code == 409 and "달라짐" in res.text
|
||
assert (me / "sangeun" / f"{b}.json").is_file()
|
||
assert _merge(api, {**MINE, "키": a}, [a], 판).status_code == 400 # 남김 = 지움
|
||
assert _merge(api, {**MINE, "키": a}, ["SK-000099"], 판).status_code == 404
|
||
|
||
|
||
def test_권한_밖_남김_둘다_담은_문서(api, world, monkeypatch):
|
||
me = world["personal"]
|
||
a = _make(me, 이름="가", 바탕={"로직": PCT})
|
||
b = _make(me, 이름="나", 바탕={"로직": PCT})
|
||
c = _make(me, 이름="다", 바탕={"로직": PCT})
|
||
both = _user(me, [a, b])
|
||
before = _sum(me, both)
|
||
판 = api.get(URL.format("personal")).json()["판"]
|
||
# 둘 다 담은 구조물 조합 = 같은 참조 두 번이 되어 못 옮김 → b 남김 · c 만 합침 · 금액 같음
|
||
res = _merge(api, {**MINE, "키": a}, [b, c], 판)
|
||
assert res.status_code == 200, res.text
|
||
assert [r["키"] for r in res.json()["합침"]] == [c]
|
||
assert [r["키"] for r in res.json()["남김"]] == [b]
|
||
assert [x["참조"]["키"] for x in _lines(me, both)[:2]] == [a, b]
|
||
assert _sum(me, both) == pytest.approx(before)
|
||
assert api.post(URL.format("personal") + "/undo", json={"키": c}).status_code == 200
|
||
|
||
|
||
def test_위층과_같으면_남김_위층_키(api, world):
|
||
me, firm = world["personal"], world["company"]
|
||
up = _make(firm, 이름="회사 것", 바탕={"로직": PCT})
|
||
mine = _make(me, 이름="내 것", 바탕={"로직": PCT})
|
||
top = _user(me, [mine])
|
||
before = _sum(me, top)
|
||
view = api.get(URL.format("personal")).json()
|
||
rows = view["묶음"][0]["문서"]
|
||
assert [(r["층"], r["키"], r["위층"]) for r in rows] == [
|
||
("company", up, True),
|
||
("personal", mine, False),
|
||
]
|
||
res = _merge(api, {**COMPANY, "키": up}, [mine], view["판"])
|
||
assert res.status_code == 200, res.text
|
||
assert _lines(me, top)[0]["참조"] == {**COMPANY, "키": up}
|
||
assert _sum(me, top) == pytest.approx(before)
|
||
assert (firm / "sangeun" / f"{up}.json").is_file()
|
||
# 위 층 문서는 지울 수 없음 · 일반 사용자는 회사 층 합치기 403
|
||
assert _merge(api, {**MINE, "키": mine}, [up], view["판"]).status_code in (403, 404)
|
||
assert api.get(URL.format("company")).status_code == 403
|
||
api.who.update(ADMIN)
|
||
assert api.get(URL.format("company")).status_code == 200
|
||
|
||
|
||
def test_일위대가_수량만_다름_꼬리표(api, world):
|
||
me = world["personal"]
|
||
line = {"고정": {"이름": "운반", "단위": "식", "단가": {"경비": 5000}}, "수량": None}
|
||
with store.at(me):
|
||
keys = [
|
||
refs.create("precombo", {"이름": n, "단위": "m", "담은로직": [line], "본뜬": src})[
|
||
"이름"
|
||
]
|
||
for n, src in (
|
||
(
|
||
"수로BOX 4.0×2.0",
|
||
{"층": "실무자료", "키": "HP-1 제1호표", "실무수량": {"줄": {"0": 3}}},
|
||
),
|
||
(
|
||
"수로BOX 2.5×2.5",
|
||
{"층": "실무자료", "키": "HP-1 제2호표", "실무수량": {"줄": {"0": 7}}},
|
||
),
|
||
)
|
||
]
|
||
got = api.get("/api/m02/layers/personal/precombo-same")
|
||
assert got.status_code == 200, got.text
|
||
group = got.json()["묶음"][0]
|
||
assert group["꼬리표"] == "수량만 다름" and [r["키"] for r in group["문서"]] == keys
|