Files
Aislo/resources/tester/test_802_4b2_m02_screen.py
T
eomsangdonandClaude Opus 5.5 58a3e039b3 feat(813-2): 보조 문서 부품 표지 73 · 작업량당 기계경비 6 지움 · 단가산출 목록에서 뺌 · 기계 → 부품 길
- 표지 = 수식 문서 머리 · 옛 로직 줄 칸 「부품」 — 기계경비식 9 · 작업량 틀 60(기종 = 기계표 상세구분 · 규격 열쇠) · 계수식 4 · 변환기가 옮김 · 틀 문서 9 장
- 지움 = 작업량당 기계경비 6(GC001145 · 1148 · 1151 · 1156 · 1159 · 1166 · 부르는 곳 0 · 요구 734-4 · 735-4) — 수식 문서 · 옛 로직 · 키대장 폐기 칸 · 빌더 · 변수표
- 이름 꼬리 4 = 원문 제목(지게운반 · 인력운반(기계설비) · 벽돌운반 · 수중펌프 설치 및 해체) · 키 폐기 0
- 서버 = `M01_MasterData_Parts` — M01 `/logics` · M02 `/logic-lib/logics` 목록에서 부품 뺌(1,440) · 키로 열기 · 부르기 그대로 · `/m01/parts`(기종 계산식 · 틀 · 계수식) · `/m02/logic-lib/machine-parts` · 연결 찾기 과녁에서 부품 뺌(1,444 → 1,435 · 판정 코드 그대로)
- 금액 전후 = 문서 1,513 · 셈 9,626(기본 + 옵션 표본) 모두 같음 · 791_1 지문 남은 1,513 같음 → 지문 다시 뜸 · 802_2 변환 대조 1,513 · 목록 xlsx 다시 씀(요약 수)
- 낡은 시험 = 1,519 · 1,518 기대 → 1,513 · GC001148 견본 → 같은 꼴 GF000089 토사깍기(기계)(블록 Q 39.293 · 합계 2,190) · 지운 6 을 셈하던 줄 걷음 · 목록 수 1,440 · 묶음 328 · 부르는 수 507
- 새 지킴 시험 test_813_2_parts

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

228 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""802-4b-2 M02 단가산출(SK) 수식 탭 = 공용 수식 문서 화면 — 임시 storage · M01 은 메모리 덮개.
① 길 앞머리 — M02 수식 탭 = 공용 화면(`createDocScreen` · `/m02/layers/{층}/sangeun/{키}/doc`) ·
옛 M02 수식 나무 화면 걷음 · 연결 후보 · 표 길 등록
② 읽기 연결 기록만으로 같은 셈(화면이 네트워크 없이 다시 셈) — 끊은 단가산출 = 끊음 문서 먼저
③ 권한 — 회사 층 일반 계정 = 읽기 · 후보 · 표만(미리 셈 · 저장 403) · 화면 = 읽기만(옵션 잠금)
④ 꺼진 줄 — 만약 0 갈래가 계산 변수를 거쳐 와도 꺼짐(폼타이 「아님」 = 세 줄) · 입력 0 안 숨김 ·
1,518 문서 기본 옵션 — 옛 판정 ⊆ 새 판정 · 꺼진 줄 금액 모두 0
"""
from __future__ import annotations
import copy
import json
import os
import subprocess
from decimal import Decimal
from pathlib import Path
import pytest
from fastapi import HTTPException
from M02_MasterTemplete import M02_Doc_Copy as dc
from M02_MasterTemplete import M02_Doc_Router as router
from M02_MasterTemplete import M02_Doc_Screen as screen
from M02_MasterTemplete import M02_Doc_Sk_Path as path
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from resources.tester.test_795_1_sk_copy import _sk
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
from resources.tester.test_m02_structure import write_sample_table
ROOT = Path(__file__).resolve().parents[2]
HERE = os.path.dirname(os.path.abspath(__file__))
FORM = "GC000211" # 합판거푸집 — 폼타이 · 측압 · 규격 옵션
BASIC = "GF000089" # 토사깍기(기계) — 굴착기 시간당 × 1 / 작업량(813-2 GC001148 지움 뒤)
ADD = {"종류": "로직", "로직": BASIC, "수량": 2, "입력값": {}, "이름": "sub3임시 굴착기"}
def _at(folder, fn, *args):
with store.at(folder):
return fn(*args)
def _src(rel: str) -> str:
return (ROOT / rel).read_text(encoding="utf-8")
def test_길_앞머리_M02_공용화면():
tabs = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Tabs.ts")
assert "createStackScreen({" in tabs and "copy: { route: (k, p) => skDocPath(" in tabs
assert "/m02/layers/${scope.layer}/sangeun/${encodeURIComponent(key)}/doc${tail}" in tabs
assert "admin: opt.edit" in tabs # 층 쓰기 권한 = 고침
scr = _src("A00_Common/line_stack_screen.ts") # 804-6 수식 탭 = 줄 쌓기
assert "cp ? cp.route(key, path) : opt.base + path" in scr
assert "{ 문서: doc, 끊음: cut, 판: version }" in scr # 저장 몸
detail = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts")
assert 'tabs.draw(/^SK-\\d{6}$/.test(doc.키 ?? "")' in detail # 저장한 SK 만 · 줄 모달 = 글
assert not (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Sangeun_Equation.ts").exists()
main = _src("main.py")
assert '__import__("M02_MasterTemplete.M02_Doc_Screen", fromlist=["router"]).router' in main
for name in ("line_stack_screen", "formula_doc_copy"):
assert len(_src(f"A00_Common/{name}.ts").splitlines()) <= 700, name
class _Replay:
"""화면 캐시 흉내 — 서버 연결 기록만으로 셈(`common_util_formula_doc_cache.ts` 짝)."""
def __init__(self, rec: dict):
self.rec = rec
self.els = {self._k(e["p"]): e for e in rec.get("요소") or []}
@staticmethod
def _k(p) -> str:
return json.dumps(p, ensure_ascii=False, sort_keys=True, default=str)
def element(self, p):
got = self.els[self._k(dc.ml._norm(p))]
if "빈" in got:
raise dc.mc.Gap(got["빈"])
if "끊김" in got:
raise dc.mc.Broken(got["끊김"])
return Decimal(str(got["값"])) if isinstance(got["값"], (int, float)) else got["값"]
def table(self, p):
return dc.ml.Links(_Tables(self.rec["표"]), {}).table(p)
def doc(self, key):
d = (self.rec.get("문서") or {}).get(key)
return json.loads(json.dumps(d), parse_float=Decimal, parse_int=Decimal) if d else d
class _Tables:
"""기록 표만 든 색인(`Links.table` 이 `master.get` 으로 찾음)."""
def __init__(self, tables: dict):
self.tables = tables
def get(self, ref):
return json.loads(json.dumps(self.tables[ref]), parse_float=Decimal, parse_int=Decimal)
def _replay(read: dict) -> dict:
doc = json.loads(json.dumps(read["문서"]), parse_float=Decimal, parse_int=Decimal)
return dc.mc.calc(doc, _Replay(read["연결"]))
def test_연결기록만으로_같은셈_끊음먼저(world):
me = world["personal"]
key = _sk(
me,
바탕={"로직": FORM},
입력값={"폼타이": "사용", "측압": "4t/㎡"},
더한줄=[copy.deepcopy(ADD)],
)
read = _at(me, path.read, key)
rec = read["연결"]
assert {"요소", "표", "문서", "이름"} <= set(rec) and BASIC in rec["문서"]
assert _replay(read)["계"] == read["셈"]["계"]
cut = copy.deepcopy(read["부름"][BASIC])
line = cut["금액"][0]
line["수량"] = f"({line.get('수량') or 1}) × 2" # 끊은 문서 안 줄 고침
saved = _at(me, path.save, key, {"끊음": {BASIC: cut}, "판": read["판"]})
assert saved["연결"]["문서"][BASIC]["금액"][0]["수량"] == line["수량"] # 끊음 먼저
assert saved["셈"]["계"] > read["셈"]["계"]
assert _replay(saved)["계"] == saved["셈"]["계"]
again = _at(me, path.read, key)
assert again["끊음"][BASIC]["금액"][0]["수량"] == line["수량"] and again["판"] == saved["판"]
got = _at(me, path.save, key, {"끊음": {}, "판": again["판"]}) # 다시 잇기
assert got["끊음"] == {} and got["셈"]["계"] == read["셈"]["계"]
def _session(role="USER"):
return {"role": role, "company_id": 7, "user_id": 42}
def test_권한_회사층_일반계정_읽기만(world):
company = world["company"]
company.mkdir(parents=True, exist_ok=True)
write_sample_table(company)
with store.at(company):
store.create_structure("rv")
key = _sk(company, 바탕={"로직": FORM})
read = router.read_doc("company", key, None, _session())
assert read["셈"]["셈됨"] and read["연결"]["표"]
body = router.DocBody(문서=read["문서"])
for fn in (router.preview_doc, router.save_doc):
with pytest.raises(HTTPException) as e:
fn("company", key, body, None, _session())
assert e.value.status_code == 403
found = screen.doc_links(
"company", key, screen.LinksBody(갈래="단가산출", 검색="굴착기 시간당"), None, _session()
)
assert any(c["키"] == "GC000267" for c in found["후보"])
assert "줄" in screen.doc_table("company", key, "QC000431", None, _session())
scr = _src("A00_Common/line_stack_screen.ts")
assert "buildOptions(doc, given, !!cp && !opt.admin," in scr # 사본 읽기만 = 옵션 잠금
copy_src = _src("A00_Common/formula_doc_copy.ts")
assert "if (!edit) return [];" in copy_src and 'createBadge(ft("Read_Only")' in copy_src
# ── ④ 꺼진 줄(TS 판정 그대로 · Node) ─────────────────────────────────
def _off(cases: list[dict]) -> list[dict]:
proc = subprocess.run(
["node", os.path.join(HERE, "helper_802_4b2_off.cjs")],
input=json.dumps(dc.ml._norm(cases), ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
return json.loads(proc.stdout)
def _vals(doc: dict, given: dict | None = None) -> dict:
links = dc.ml.Links(dc.skc.whole_(), dc.folder())
return dc.mc.calc(doc, links, given).get("값") or {}
def test_꺼진줄_계산변수_거침():
doc = dc.folder()[FORM]
names = [x["이름"] for x in doc["금액"]]
tie = [i for i, n in enumerate(names) if n.startswith("폼타이")]
hole = [i for i, n in enumerate(names) if "구멍땜" in n]
assert len(tie) == 3 and hole
off, on, on2 = _off(
[
{"문서": doc, "값": _vals(doc)}, # 폼타이 아님 · 규격 12.7
{"문서": doc, "값": _vals(doc, {"폼타이": "사용"})},
{"문서": doc, "값": _vals(doc, {"폼타이": "사용", "폼타이규격": "9.5㎜"})},
]
)
assert set(tie) <= set(off["새"]) and tie[0] not in off["옛"] # Q_조 = 만약 0 을 거침
assert set(on["새"]) == set(tie) - {tie[0]} and set(on2["새"]) == set(tie) - {tie[1]}
assert not set(hole) & set(off["새"]) # 구멍땜 0 개소 = 입력 0 · 안 숨김
plain = {
"식": [{"기호": "Z", "식": "0"}, {"기호": "Y", "식": "만약(S == 'a', 0, 2)"}],
"금액": [
{"이름": "글 0", "비목": "노무비", "수량": "Z × 3", "단가": "w"},
{"이름": "입력 0", "비목": "노무비", "수량": "X × 3", "단가": "w"},
{"이름": "거침", "비목": "노무비", "수량": "Y × 3 ÷ 10", "단가": "w"},
{"이름": "합", "비목": "노무비", "수량": "Y + 1", "단가": "w"},
{"이름": "순환", "비목": "노무비", "수량": "V", "단가": "w"},
],
}
plain["식"].append({"기호": "V", "식": "V × 2"})
got = _off([{"문서": plain, "값": {"S": "a", "Z": 0, "X": 0, "Y": 0, "w": 5}}])[0]
assert got["새"] == [2]
def test_꺼진줄_전수_1513():
docs = dc.folder()
links = dc.ml.Links(dc.skc.whole_(), docs)
keys, cases, rows = [], [], []
for key, doc in docs.items():
ans = dc.mc.calc(doc, links)
keys.append(key)
cases.append({"문서": doc, "값": ans.get("값") or {}})
rows.append(ans.get("줄") or [])
got = _off(cases)
assert len(keys) >= 1513 # 813-2 지움 6
new = sum(len(g["새"]) for g in got)
assert all(set(g["옛"]) <= set(g["새"]) for g in got) and new > sum(len(g["옛"]) for g in got)
for key, g, r in zip(keys, got, rows):
for i in g["새"]:
amt = r[i].get("금액") if i < len(r) else None
assert amt is None or float(amt) == 0, (key, i, amt)