- M02 SK 「수식」 탭 = M01 과 같은 공용 화면(`createDocScreen` · 길 앞머리만 `/m02/layers/{층}/sangeun/{키}/doc`) — 옛 M02 수식 나무 화면(`_Sangeun_Equation.ts` · 796-2b · 2c · 2e · 800 · 801 꼴) 걷음
· 옵션 = SK 문서 값(저장) · [단가 갱신] · [재생성](미리 셈 → 알림 계 전 → 후 · 버리는 것 → 저장) · 「M01 새 판 있음」 배지(`formula_doc_copy.ts`)
· 단가산출 연결 펼침 머리 「연결 / 끊음」 · [연결 끊기] · [다시 잇기] · 끊은 문서 안 줄 그 자리 고침(`formula_doc_sub.ts`) · 저장 몸 {문서, 끊음, 판}
· 층 쓰기 권한 없음 = 읽기만(옵션 잠금 · 단추 없음 · 「읽기만」) · 판 바뀜 409 = 사본 같으면 새 판으로 한 번 더 · 저장 뒤 페이지 판 · 상세 탭 `수식` 칸 새로
· 저장 안 한 SK(일위대가 줄 모달) = 「저장한 단가산출만」 글
- 서버 — 수식 길 답에 `연결`(화면 캐시 기록 · `M02_Doc_Screen.record`) · 연결 후보 · 표 길(층 읽기 · M01 후보 · 표 그대로 · main 등록)
- 꺼진 줄(요구 663-1 마저) — 곱 인수 하나가 선택으로 꺼진 0(만약 0 갈래 · 식 줄 기호면 그 식으로 거침)이면 숨김 · 입력 · 글 0 은 그대로
· 판정을 순수 파일로(`common_util_formula_doc_view.ts` · Node 로 시험) · 1,518 문서 기본 옵션 꺼짐 158 → 203 줄(꺼진 줄 금액 모두 0) · GC000211 폼타이 아님 = 세 규격 줄 모두
- 화면 파일 700 줄 — 고침 함수 · 옵션 가로 · 연결 펼침을 새 파일로(`formula_doc_edit` · `_options` · `_sub`)
- 시험 test_802_4b2(5 · 헬퍼 helper_802_4b2_off.cjs) · 옛 시험 둘 고침 — test_793_3b · test_796_2b 가 걷은 옛 M02 수식 화면 글을 읽음 → 없음 확인으로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
192 lines
8.7 KiB
Python
192 lines
8.7 KiB
Python
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
||
"""796-2b M02 단가산출 수식 탭 — 사본 줄 수식 · 고침 = 금액 · 저장 = 사본 위 정본(임시 storage).
|
||
|
||
① 사본 줄 수식 읽기 = SK 금액 ② 고침 미리 셈 = 금액 바뀜(저장 없음) ③ [저장] = 서버가 다시 얹어 셈 ·
|
||
틀린 고침 400 · M01 그대로 ④ 변수 더함 · 지움(쓰는 곳 막음) · 기호 · 첨자 전파 ⑤ 권한 = 층 쓰기
|
||
⑥ 가져온 식 SK 같은 길 ⑦ M01 바뀌어도 그대로 · [재생성] = 고침 다시 얹음 · 못 얹음 알림.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import copy
|
||
|
||
import pytest
|
||
from fastapi import HTTPException
|
||
|
||
from M01_MasterData import M01_MasterData_Store as m01
|
||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
||
from M02_MasterTemplete import M02_Sangeun_Equation as eqm
|
||
from M02_MasterTemplete import M02_Sangeun_Imported as imp
|
||
from M02_MasterTemplete import M02_Sangeun_Router as router
|
||
from resources.tester.test_795_1_sk_copy import (
|
||
_calc,
|
||
_doc,
|
||
_fake,
|
||
_imported_row,
|
||
_live,
|
||
_save,
|
||
_sk,
|
||
_sum,
|
||
)
|
||
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
|
||
|
||
KEY = "GC000211" # 합판거푸집 — 중간 시공량 · 형틀목공 · 보통인부 · 호표 0 = 형틀목공
|
||
DOUBLE = [{"할일": "식", "자리": "호표:0", "식": "형틀목공 × 2", "까닭": "sub2임시 시험"}]
|
||
|
||
|
||
def _line(eq, key, kind=None):
|
||
return next(x for x in eq["줄"] if x.get("열쇠") == key and (kind is None or x["갈래"] == kind))
|
||
|
||
|
||
def _read(folder, doc):
|
||
with store.at(folder):
|
||
return eqm.read(doc)
|
||
|
||
|
||
def _preview(folder, doc, edits):
|
||
with store.at(folder):
|
||
return eqm.preview(doc, edits)
|
||
|
||
|
||
def test_read_copy_row_equals_sk_amount(world):
|
||
"""① 사본 줄 수식 — 합계 = SK 금액 = M01 셈 · 키보드 글."""
|
||
me = world["personal"]
|
||
key = _sk(me, 바탕={"로직": KEY})
|
||
got = _read(me, _doc(me, key))
|
||
assert got["ok"], got
|
||
eq = got["수식"]
|
||
assert eq["열쇠"] == KEY and eq["합계"]["계"] == _sum(me, key)
|
||
assert eq["합계"]["계"] == labor.run_line(_live(), KEY, {})["기준"]["계"]
|
||
assert _line(eq, "형틀목공", "호표")["입력글"] == "Q_형틀목공"
|
||
|
||
|
||
def test_edit_changes_amount_save_is_canonical(world):
|
||
"""② · ③ 고침 미리 셈 = 금액 바뀜 · [저장] = 고침 목록 남음 · 서버 셈 = 미리 셈 · M01 그대로."""
|
||
me = world["personal"]
|
||
key = _sk(me, 바탕={"로직": KEY})
|
||
was = _sum(me, key)
|
||
m01_row = copy.deepcopy(_live().logic(KEY))
|
||
got = _preview(me, _doc(me, key), DOUBLE)
|
||
assert got["되나"], got["까닭"]
|
||
assert got["전"]["계"] == was and got["후"]["계"] > was
|
||
assert got["줄"]["호표"][0]["수량"] == "형틀목공 * 2"
|
||
assert _sum(me, key) == was # 미리 셈 = 저장 없음
|
||
doc = {**_doc(me, key), eqm.KEY: DOUBLE}
|
||
_save(me, key, doc)
|
||
saved = _doc(me, key)
|
||
assert saved[eqm.KEY][0]["까닭"] == "sub2임시 시험"
|
||
assert saved["사본"]["로직"][0]["호표"][0]["수량"] == m01_row["호표"][0]["수량"] # 사본 = 원래
|
||
now = _calc(me, saved)
|
||
assert now["기준"]["계"] == got["후"]["계"]
|
||
assert now["원래"]["계"] == was # 원래 대비 = 고침 없는 사본
|
||
assert _read(me, saved)["수식"]["합계"]["계"] == got["후"]["계"]
|
||
assert _live().logic(KEY) == m01_row # M01 정본 안 바뀜
|
||
bad = {**saved, eqm.KEY: [{"할일": "식", "자리": "호표:0", "식": "없는값 × 2"}]}
|
||
with pytest.raises(store.StoreError):
|
||
_save(me, key, bad)
|
||
assert _sum(me, key) == got["후"]["계"]
|
||
|
||
|
||
def test_variables_add_drop_rename(world):
|
||
"""④ 변수 더함 → 식에서 부름 · 쓰는 변수 지움 막음 · 기호 · 첨자 = 쓰는 식 모두."""
|
||
me = world["personal"]
|
||
key = _sk(me, 바탕={"로직": KEY})
|
||
doc = _doc(me, key)
|
||
add = [
|
||
{"할일": "더함", "갈래": "중간", "열쇠": "할증", "식": "1.5", "뒤": "보통인부"},
|
||
{"할일": "식", "자리": "호표:1", "식": "보통인부 × 할증"},
|
||
]
|
||
got = _preview(me, doc, add)
|
||
assert got["되나"], got["까닭"]
|
||
assert got["후"]["노무비"] > got["전"]["노무비"]
|
||
assert "할증" in [v["열쇠"] for v in got["수식"]["변수표"]]
|
||
got = _preview(me, doc, [{"할일": "지움", "열쇠": "시공량"}])
|
||
assert not got["되나"] and "형틀목공" in " ".join(got["까닭"])
|
||
sign = [{"할일": "변수", "열쇠": "시공량", "기호": "Q", "아래": "d", "이름": "하루 시공량"}]
|
||
got = _preview(me, doc, sign)
|
||
assert got["되나"] and got["후"] == got["전"] # 이름만 — 금액 그대로
|
||
assert "Q_d" in _line(got["수식"], "형틀목공", "중간")["입력글"]
|
||
assert "Q_d" in _line(got["수식"], "보통인부", "중간")["입력글"]
|
||
_save(me, key, {**doc, eqm.KEY: sign})
|
||
again = _read(me, _doc(me, key))["수식"]
|
||
assert next(v for v in again["변수표"] if v["열쇠"] == "시공량")["기호"] == "Q"
|
||
|
||
|
||
def _session(role="USER"):
|
||
return {"role": role, "company_id": 7, "user_id": 42}
|
||
|
||
|
||
def test_permission_layer_write(world, monkeypatch):
|
||
"""⑤ 미리 셈 = 그 층 쓰기 — 마스터 = 시스템 관리자 · 회사 = 회사 관리자 · 개인 = 본인."""
|
||
me = world["personal"]
|
||
key = _sk(me, 바탕={"로직": KEY})
|
||
body = router.EquationBody(문서=_doc(me, key), 고침=DOUBLE)
|
||
for layer in ("system", "company"):
|
||
with pytest.raises(HTTPException) as e:
|
||
router.sangeun_equation_preview(layer, body, None, _session())
|
||
assert e.value.status_code == 403
|
||
got = router.sangeun_equation_preview("personal", body, None, _session())
|
||
assert got["되나"] and got["후"]["계"] > got["전"]["계"]
|
||
got = router.sangeun_equation("personal", body, None, _session())
|
||
assert got["ok"] and got["수식"]["합계"]["계"] == got["수식"]["합계"]["계"]
|
||
monkeypatch.setattr(router, "_root", lambda *a, **k: me)
|
||
admin = router.sangeun_equation_preview("system", body, None, _session("SYSTEM_ADMIN"))
|
||
assert admin["되나"]
|
||
|
||
|
||
def test_imported_same_path(world):
|
||
"""⑥ 가져온 식 SK(바탕 null · 가져온.로직) — 같은 읽기 · 고침 · 저장."""
|
||
me = world["personal"]
|
||
with store.at(me):
|
||
key = imp.create(_imported_row(), None, "sub2임시 가져온")["이름"]
|
||
doc = _doc(me, key)
|
||
got = _read(me, doc)
|
||
assert got["ok"], got
|
||
assert got["수식"]["합계"]["계"] == _sum(me, key)
|
||
edit = [{"할일": "식", "자리": "호표:0", "식": "0.5 × 2"}]
|
||
got = _preview(me, doc, edit)
|
||
assert got["되나"], got["까닭"]
|
||
assert got["후"]["노무비"] == pytest.approx(got["전"]["노무비"] * 2, abs=1)
|
||
_save(me, key, {**doc, eqm.KEY: edit})
|
||
now = _calc(me, _doc(me, key))
|
||
assert now["기준"]["노무비"] == pytest.approx(got["후"]["노무비"], abs=1)
|
||
assert _doc(me, key)["가져온"]["로직"]["호표"][0]["수량"] == "0.5" # 엑셀 줄 그대로
|
||
|
||
|
||
def test_m01_change_kept_regen_reapplies(world, monkeypatch):
|
||
"""⑦ M01 바뀌어도 고친 SK 그대로 · [재생성] = 새 사본에 고침 다시 · 자리 없으면 못얹은."""
|
||
me = world["personal"]
|
||
key = _sk(me, 바탕={"로직": KEY})
|
||
_save(me, key, {**_doc(me, key), eqm.KEY: DOUBLE})
|
||
was = _sum(me, key)
|
||
row = copy.deepcopy(_live().logic(KEY))
|
||
row["호표"][1]["수량"] = f"({row['호표'][1]['수량']}) * 3"
|
||
_fake(monkeypatch, [row])
|
||
assert _sum(me, key) == was
|
||
with store.at(me):
|
||
got = skc.preview(_doc(me, key), skc.REGEN)
|
||
assert not got["못얹은"] and got["후"]["계"] > was # 새 판 + 같은 고침
|
||
gone = copy.deepcopy(row)
|
||
gone["호표"] = []
|
||
_fake(monkeypatch, [gone])
|
||
with store.at(me):
|
||
got = skc.preview(_doc(me, key), skc.REGEN)
|
||
assert eqm.LOST in [x["cid"] for x in got["못얹은"]] # 그 밖 = 없어진 줄 M01판(790-2)
|
||
assert m01.loaded()[1].logic(KEY)["호표"] == []
|
||
|
||
|
||
def test_screen_same_part_first_tab():
|
||
"""화면 — M02 수식 = 공용 수식 문서 화면(802-4b-2 · 옛 수식 나무 화면 걷음) · 첫 탭 · 옛 탭 걷음(799-2)."""
|
||
import re
|
||
from pathlib import Path
|
||
|
||
root = Path(__file__).resolve().parents[2] / "M02_MasterTemplete"
|
||
assert not (root / "M02_MasterTemplete_Sangeun_Equation.ts").exists()
|
||
tabs = (root / "M02_MasterTemplete_Sangeun_Tabs.ts").read_text(encoding="utf-8")
|
||
views = re.findall(r'\{ value: "(\w+)", label: ', tabs)
|
||
# 799-2 — 시트 · 인력·재료·경비 · 시험 계산 · 텍스트 수식 걷음
|
||
assert views == ["equation", "ho"]
|
||
assert 'show("equation");' in tabs
|