Files
Aislo/resources/tester/test_703_b09_daega_copy.py
T
eomsangdonandClaude Opus 5.5 b68a246602 feat(742-1): 일위대가 안 줄 수량 = 사용자 입력 저장 · 기준단위 단가 / 수량 반영 금액 열
- 723-1 「안 줄 수량 null」 걷음 — SC 줄 · 자재 수량 {손} 저장(새 줄 1 · 빈 칸 = 「수량 입력」 · 금액 빠짐)
- 셈 차례: 층 = 알약 값 → 저장 수량 · 공사 굳힌 사본 = 굳힌 손 → 알약 → 저장(with_pills 가 {저장} 칸으로 옮김)
- 불러오기로 만든 일위대가 = 엑셀 수량을 저장 수량으로(본뜬.실무수량 같은 값 남김)
- 단가산출 안 일위대가 = 저장 수량으로 금액(수량 없음 없어짐) · B08 자재 · B09 내 자료집 저장도 같은 차례
- 일위대가표 수량 칸 = 문서에 저장(휘발 임시 칸 걷음) · 합계 열 「기준단위 단가」 · 「수량 반영 금액」
- 낡은 시험(723 · 175 · 204-2 · 702-3 · 703 · 717-4 · 719-1 · 719-2 · hopyo build · flow · labor ref)의 null 단언을
  저장 수량 단언으로 고침 — 723-1 결정이 742-1(요구 584-1)로 바뀜 · 새 지킴 시험 test_742_1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:03:48 +09:00

228 lines
11 KiB
Python

"""703-2 B09 펼친 일위대가 우클릭 — 복사해 새 일위대가 · 내 자료집에 저장.
- 복사본 = 덮개 `K:…` 의 `@copy` {from · name · spec} — 셈 짝(`Calc_Labor` 파이썬 · TS 거울)이
원본 블록을 본떠 끝 번호 새 호표 · 덮개 = 제 것 · 원본 없음 = 짝 잃음 ·
저장 다듬기 · 서고 사본 지킴(prune)
- 내 자료집 = 개인 층 사전조합(임시 storage) — 줄 수량 덮개 = 문서 손(쓸 때 걷힘 · 723-1) ·
품 · 단가 덮개 = 안 담음 수 ·
같은 이름 409 → 덮어쓰기 · 새 이름
"""
import copy
import importlib.util
import json
import sys
from pathlib import Path
from typing import Any
import pytest
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B09_Estimation import B09_Estimation_Bill_Library as library # noqa: E402
from B09_Estimation import B09_Estimation_Daega_Personal as personal # noqa: E402
from B09_Estimation import B09_Estimation_Store as store # noqa: E402
from B09_Estimation.B09_Estimation_Calc_Labor import compute_labor # noqa: E402
from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs # noqa: E402
from config import config_system # noqa: E402
from M02_MasterTemplete import M02_MasterTemplete_Items as items_store # noqa: E402
from M02_MasterTemplete import M02_MasterTemplete_Store as m02 # noqa: E402
from M02_MasterTemplete import M02_Template_Layers as layers # noqa: E402
def _load(name: str):
spec = importlib.util.spec_from_file_location(name, Path(__file__).with_name(f"{name}.py"))
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
b145 = _load("test_145_2_b09_labor_calc")
b702 = _load("test_702_b09_daega_cover")
#: DG9 찰붙임 복사 — 석공 품 0.1 → 0.2(복사본만) · 원본 DG9 덮개 0.12 는 원본에만
COPY = {
"T:DG9": {"labor:L00005#0": {"qty": 0.12}},
"K:aa": {
"@copy": {"from": "T:DG9", "name": "찰붙임 복사", "spec": "규격 B"},
"labor:L00005#0": {"qty": 0.2},
},
"K:bb": {"@copy": {"from": "K:aa", "name": "복사의 복사"}},
"K:cc": {"@copy": {"from": "T:없음"}, "x#0": {"qty": 1}},
}
def _block(out, bid):
return next(b for b in out["daega_blocks"] if b["id"] == bid)
def test_복사본_새_호표_끝_번호_덮개_제것():
daega = b702._covered_excel()
out = compute_labor(daega, b145.SHEETS, b145.SANGEUN, COPY)
src, mine = _block(out, "DG9"), _block(out, "DG:K:aa")
n = len(daega["blocks"])
assert [mine["no"], _block(out, "DG:K:bb")["no"]] == [n + 1, n + 2] # 번호 새로(끝)
assert (mine["name"], mine["spec"], mine["cover"]) == ("찰붙임 복사", "규격 B", "K:aa")
assert src["lines"][0]["qty"] == 0.12 and mine["lines"][0]["qty"] == 0.2
assert mine["lines"][0]["base"]["qty"] == 0.1 # 품셈 값 = 원본 기본값
assert [ln["amount"] for ln in mine["lines"][1:]] == [ln["amount"] for ln in src["lines"][1:]]
twice = _block(out, "DG:K:bb")
assert twice["spec"] == "규격 B" and twice["lines"][0]["qty"] == 0.1 # 덮개 안 물려받음
assert any(r["key"] == "DG:K:aa" for r in out["daega"]) # 목록표 = 내역 단가연결 자리
assert out["daega_orphans"] == [
{"cover": "K:cc", "cid": "@copy"},
{"cover": "K:cc", "cid": "x#0"},
]
def test_거울_파이썬_TS_복사본():
daega = b702._covered_excel()
py = json.loads(json.dumps(compute_labor(daega, b145.SHEETS, b145.SANGEUN, COPY)))
ts = b145._ts(daega, b145.SHEETS, b145.SANGEUN, COPY)
assert b145._same(py, ts["out"])
def test_저장_다듬기_복사본_표지():
raw = {"daega_cover": {**COPY, "K:dd": {"@copy": {"name": "원본 없음"}}}}
got = store.clean_user(raw)["daega_cover"]
assert got["K:aa"]["@copy"] == {"from": "T:DG9", "name": "찰붙임 복사", "spec": "규격 B"}
assert got["K:bb"] == {"@copy": {"from": "K:aa", "name": "복사의 복사"}}
assert "K:dd" not in got
def test_서고_사본_원본_지킴():
user = {"daega_cover": {"K:aa": {"@copy": {"from": "L:combo@system@-@SS1"}}}}
assert library.used_folders(user) == {"combo@system@-@SS1"}
def test_사전조합_원본_복사본_사본_지킴(tmp_path):
"""통합 점검 — 사전조합(`C:참조`) 복사본 · 복사본의 복사본 = 원본 사본 폴더 지킴(업데이트 수요 · 저장 정리)."""
base = library.library_dir(tmp_path)
for name, ref in (
("precombo@personal@1-3@SC-000001", {"층": "personal", "주인": "1/3", "키": "SC-000001"}),
("precombo@personal@1-3@SC-000002", {"층": "personal", "주인": "1/3", "키": "SC-000002"}),
):
(base / name).mkdir(parents=True)
manifest = {"종류": "precombo", "원본": {"참조": ref}}
(base / name / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
src = "C:참조|personal|1/3|SC-000001"
user = {
"daega_cover": {
"K:aa": {"@copy": {"from": src, "name": "복사"}},
"K:bb": {"@copy": {"from": "K:aa", "name": "복사의 복사"}},
}
}
assert library.used_folders(user) == set() # 폴더 모름 = 옛 꼴
assert library.used_folders(user, tmp_path) == {"precombo@personal@1-3@SC-000001"}
assert library.prune(tmp_path, user) == ["precombo@personal@1-3@SC-000002"]
# ── 내 자료집(개인 층 사전조합 · 임시 storage) ─────────────────────────────
@pytest.fixture()
def world(tmp_path, monkeypatch) -> dict[str, Any]:
(tmp_path / "storage").mkdir()
monkeypatch.setattr(config_system, "STORAGE_BASE_DIR", str(tmp_path / "storage"))
root = b145._project(tmp_path / "proj", [(100, "SS000901")])
b145._snap(root, "pipe@100.0", "SS000901", b145.LINES, b145._values(b145.STONE))
folder = layers.personal_dir(7, 42)
folder.mkdir(parents=True)
items_store.ensure(folder)
got = daega_inputs(root)
block = next(b for b in got["blocks"] if b["id"] == got["owners"]["pipe@100.0"])
return {"root": root, "folder": folder, "block": block}
def test_자료집_문서_덮개_손_안담음_수(world):
block = world["block"]
each = block["lines"][1]["qty"] # GF000119 줄 수량(산근 = 손 그대로 0.5)
cover = {
block["cover"]: {
"sangeun:SG:GF000119#0": {"qty": 0.8, "labor": 1000}, # 줄 수량 = 담음 · 단가 = 안 담음
}
}
made = personal.build(world["root"], block["cover"], cover, "내 돌붙임")
doc = made["문서"]
assert each == 0.5 and doc["이름"] == "내 돌붙임"
hands = [ln["수량"]["손"] for ln in doc["담은로직"]]
assert hands == [2, 0.8, 1] # 덮개 줄만 바뀜
assert doc["담은로직"][0]["입력값"] == b145.STONE # 넣을 값 얹음
assert made["담음"] == {"수량": 1, "넣을값": 1, "고른자재": 0}
assert made["안담음"] == {"품": 0, "단가": 1}
# 복사본 = 원본까지 따라감 · 이름 기본 = 복사본 이름 · 덮개 = 복사본 것
cover2 = {"K:aa": {"@copy": {"from": block["cover"], "name": "복사"}}}
cover2["K:aa"]["sangeun:SG:GF000219#0"] = {"qty": 3}
made2 = personal.build(world["root"], "K:aa", cover2)
assert made2["문서"]["이름"] == "복사"
assert [ln["수량"]["손"] for ln in made2["문서"]["담은로직"]] == [3, 0.5, 1]
with pytest.raises(LookupError):
personal.build(world["root"], "S:없음", {})
def test_자료집_같은_이름_물음(world):
block = world["block"]
doc = personal.build(world["root"], block["cover"], {}, "내 돌붙임")["문서"]
with m02.at(world["folder"]):
first = personal.write(doc)
with pytest.raises(m02.StoreError) as e:
personal.write(doc)
assert e.value.status == 409 and e.value.detail["같은이름"] == first["이름"]
changed = copy.deepcopy(doc)
changed["담은로직"][1]["수량"] = {"손": 9}
again = personal.write(changed, first["이름"]) # 덮어쓰기
assert again["이름"] == first["이름"]
with pytest.raises(m02.StoreError) as e: # 733-2 — 새 이름이라도 같은 내용 = 409 물음
personal.write({**doc, "이름": "내 돌붙임 2"})
assert (
e.value.detail["같은것"][0]["키"] == first["이름"] and "같은이름" not in e.value.detail
)
other = personal.write({**doc, "이름": "내 돌붙임 2"}, new=True) # 그래도 새로
used = personal.write({**doc, "이름": "내 돌붙임 3"}, use=first["이름"]) # 그것을 씀
assert used == {"이름": first["이름"], "씀": True}
assert other["이름"] != first["이름"]
saved = json.loads(
(world["folder"] / "precombo" / f"{first['이름']}.json").read_text(encoding="utf-8")
)
# 742-1 = M02 SC 안 줄 수량 저장(덮어쓴 손 9 · 쓰는 곳 알약 · 이 공사 입력이 이어지면 그 값이 앞섬)
assert saved["이름"] == "내 돌붙임" and saved["담은로직"][1]["수량"] == {"손": 9}
assert saved["담은로직"][0]["입력값"] == b145.STONE
assert len(list((world["folder"] / "precombo").glob("SC-*.json"))) == 2
def test_자료집_길_같은_이름_409_덮어쓰기(world, monkeypatch):
"""서버 길 — 화면 캐시 덮개째 · 개인 층(임시 storage) · 같은 이름 409 {같은이름} → 덮어쓰기."""
from fastapi import FastAPI
from fastapi.testclient import TestClient
from B09_Estimation import B09_Estimation_Router as b09
from common_util.common_util_auth import verify_session
async def root_of(_pid):
return world["root"]
monkeypatch.setattr(b09, "_project_root", root_of)
app = FastAPI()
app.include_router(b09.router)
session = {"user_id": 42, "company_id": 7, "role": "USER", "is_master": False}
app.dependency_overrides[verify_session] = lambda: session
client = TestClient(app)
block = world["block"]
url = "/api/projects/8b98ca75-c572-4002-8468-2adae4323414/estimation/daega/personal"
user = {"daega_cover": {block["cover"]: {"sangeun:SG:GF000219#0": {"qty": 4}}}}
body = {"cover": block["cover"], "name": "길 시험", "user": user}
first = client.post(url, json=body)
assert first.status_code == 200, first.text
assert first.json()["담음"]["수량"] == 1
again = client.post(url, json=body)
assert again.status_code == 409 and again.json()["detail"]["같은이름"] == first.json()["키"]
over = client.post(url, json={**body, "overwrite": first.json()["키"]})
assert over.status_code == 200 and over.json()["키"] == first.json()["키"]
saved = world["folder"] / "precombo" / f"{first.json()['키']}.json"
assert json.loads(saved.read_text(encoding="utf-8"))["담은로직"][0]["수량"] == {
"손": 4
} # 742-1
assert client.post(url, json={**body, "cover": "S:없음"}).status_code == 404