Merge branch 'sub_laptop_1' of C:/Aislo_wt/sub_laptop_1 into main_laptop_1

This commit is contained in:
2026-11-08 16:32:28 +09:00
21 changed files with 624 additions and 844 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ def test_화면_서고_모드도_자재목록_세_칸_자재_저장(world): # n
read("M02_MasterTemplete_Labor_Table.ts"),
)
modal = read("M02_MasterTemplete_Labor_RowModal.ts")
assert 'if (opt.mode === "precombo") return mountLaborTable(host, doc, opt);' in detail
assert "return mountLaborTable(host, doc, opt);" in detail # 806-1 — 조합도 같은 부품
assert 'className: "ui-btn-group", children: [addLogic]' in lib and "addMat" not in lib
assert "setLogicMark(row, at, key, v);" in modal and "setMark(o.mat, key, v);" in modal
assert "delete out.자재" not in lib and "delete out.열" in lib
+4 -3
View File
@@ -131,9 +131,10 @@ def test_사전조합_안_줄도_별도계상_뺌():
def test_화면_별도계상_바꾸면_다시_셈():
detail = open(os.path.join(SRC, "M02_MasterTemplete_Labor_Detail.ts"), encoding="utf-8").read()
assert 'if (key === "별도계상") changed(true);' in detail # 사용자 자재 = 짝 셈
assert 'if (key === "별도계상") changed();' in detail # 로직 자재 = 서버 기준
# 806-1 — 조합 · 서고 모두 일위대가표 부품 · 세 칸은 줄 모달(`Labor_RowModal`)
modal = open(os.path.join(SRC, "M02_MasterTemplete_Labor_RowModal.ts"), encoding="utf-8").read()
assert 'o.done(key === "별도계상");' in modal # 사용자 자재 · 로직 자재 모두 별도계상만 다시 셈
assert modal.count('o.done(key === "별도계상");') == 2
# ── ③ 원문 여럿 ──────────────────────────────────────────────────────
+2 -2
View File
@@ -137,8 +137,8 @@ def test_M02_문서_calc_를_격자_줄로():
def test_세_곳_같은_부품():
detail = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts").read_text("utf-8")
assert "laborGrid(cur, calc ?? last, logics, " in detail # 카드 · 상세 = 같은 Labor_Detail
table = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Labor_Table.ts").read_text("utf-8")
assert "laborBlock(cur, calc ?? last, logics)" in table # 카드 · 상세 · 서고 = 같은 Labor_Table(806-1)
m02 = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Labor_Grid.ts").read_text("utf-8")
b09 = (ROOT / "B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts").read_text("utf-8")
assert '"@ui/ui_template_hopyo_grid"' in m02 and '"@ui/ui_template_hopyo_grid"' in b09
+19 -11
View File
@@ -9,6 +9,7 @@
"""
import json
import re
import shutil
import subprocess
from pathlib import Path
@@ -153,16 +154,22 @@ def test_normalizeLabor_줄_패치_지킴():
def test_화면_이음():
"""줄 단추 = 로직 줄 · SK 참조 줄만 · 셈 주입(프로젝트 복사본)엔 없음 · 적용 안 한 고침 물음 · 700 줄."""
rows = (M02 / "M02_MasterTemplete_Labor_Rows.ts").read_text("utf-8")
assert 'sangeun && !fx && (!row.참조 || row.참조.키.startsWith("SK-"))' in rows
"""806-1 — 조합 일위대가도 일위대가표 한 부품 · 단가산출(SK) 줄 = 그 SK 문서 모달(`Labor_SkEdit`) · 옛 조합 전용
단가산출 보기 모달 걷음 · 셈 주입(프로젝트 복사본)엔 옛 줄 모달 · 고친 것 있이 닫으면 물음 · 700 줄."""
detail = (M02 / "M02_MasterTemplete_Labor_Detail.ts").read_text("utf-8")
assert "const sk = opt.calc ? {} : { sangeun: openSk };" in detail
assert 'edit && opt.layer === "personal" && !library.owner' in detail
modal = (M02 / "M02_MasterTemplete_Labor_SangeunModal.ts").read_text("utf-8")
assert "mountRowSk(host," in modal # 724-1 — 줄 모드 SK 상세(공용 상세)
assert "!done && sk.take() && !(await askConfirm(" in modal # 적용 안 한 고침 물음
assert "saveAsSk(o.row, now(), name, o.unit)" in modal # 새 SK = 내 개인 층(saveAsSk)
assert "return mountLaborTable(host, doc, opt);" in detail
assert not (M02 / "M02_MasterTemplete_Labor_SangeunModal.ts").exists()
table = (M02 / "M02_MasterTemplete_Labor_Table.ts").read_text("utf-8")
assert "if (isSkRef(row.참조) && !opt.calc)" in table and "openSkEdit({" in table
assert "readOnly: !edit || row.참조.층 !== opt.layer," in table # 위 층 SK = 읽기만
assert "closed: (done) => alive && done && recalc()," in table # 저장했으면 금액 다시 셈
modal = (M02 / "M02_MasterTemplete_Labor_SkEdit.ts").read_text("utf-8")
assert "mountSangeunDetail(host, doc," in modal and "source: true" in modal
assert re.search(
r'saveTemplate\(\s*layer,\s*"sangeun",\s*opt\.ref\.키', modal
) # 저장 = 그 SK 문서(그 층 · 그 주인)
assert "await handle.flush();" in modal # 수식 저장 먼저
assert "dirty && !saved && !(await showConfirmDialog(" in modal # 고친 것 물음
sk = (M02 / "M02_MasterTemplete_Labor_RowSk.ts").read_text("utf-8")
assert '{ layer: "personal", owner: null }' in sk
cover = (M02 / "M02_MasterTemplete_Sangeun_Cover.ts").read_text("utf-8")
@@ -170,8 +177,9 @@ def test_화면_이음():
assert "coverTable" not in cover and not (M02 / "M02_Sangeun_Cover_Model.ts").exists()
for name in (
"M02_MasterTemplete_Labor_Rows.ts",
"M02_MasterTemplete_Labor_Detail.ts",
"M02_MasterTemplete_Labor_SangeunModal.ts",
"M02_MasterTemplete_Labor_Table.ts",
"M02_MasterTemplete_Labor_Combo.ts",
"M02_MasterTemplete_Labor_SkEdit.ts",
"M02_MasterTemplete_Sangeun_Cover.ts",
"logic/M02_Sangeun_Cover_Model.ts",
):
+1 -2
View File
@@ -116,8 +116,7 @@ def test_736_1_항목_추가_찾기_창_하나():
assert "[로직 추가]" not in locale and "기초는 [항목 추가]로" in locale
assert 'AddLogic: ["항목 추가", "Add item"]' in _text(M02 / "M02_MasterTemplete_Labor_Text.ts")
table = _text(M02 / "M02_MasterTemplete_Labor_Table.ts")
detail = _text(M02 / "M02_MasterTemplete_Labor_Detail.ts")
for text in (table, detail):
for text in (table,): # 806-1 — 조합 · 서고 = 같은 Labor_Table
assert "addMaterialButton" not in text and "addMat" not in text
assert "material: (m: {" in text # 재료 고름 → 「자재 목록」
assert '"Labor_InLogicMat"' in text and '"Labor_AlreadyMat"' in text # 중복 검사 그대로
+2 -2
View File
@@ -39,8 +39,8 @@ def test_화면_서고_상세는_일위대가표만():
_src("M02_MasterTemplete_Labor_Table.ts"),
_src("M02_MasterTemplete_Labor_Detail.ts"),
)
assert 'if (opt.mode === "precombo") return mountLaborTable(host, doc, opt);' in detail
assert "calc = settle(calc, cur, {});" in table # 짝 셈 바로(742-1 수량 = 문서)
assert "return mountLaborTable(host, doc, opt);" in detail # 806-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 # 우클릭 메뉴
+2 -1
View File
@@ -133,7 +133,8 @@ def test_화면_수량_칸_저장_새_줄_1_두_칸():
table = _src("M02_MasterTemplete_Labor_Table.ts")
assert "const temp = new Map" not in table and "withTemp" not in table # 휘발 칸 걷음
assert "row.수량 = raw ? { 손: v } : null;" in table and "changed(true);" in table
assert table.count("수량: { ...ONE }") == 4 # 기초 · 참조 · 고정 · 자재 새 줄 = 1
assert table.count("수량: fresh()") == 4 # 기초 · 참조 · 고정 · 자재 새 줄 = 서고 1 · 조합 면적 따름(806-1)
assert "const fresh = (): LaborRow" in table and "{ ...ONE }" in table
assert 'BasePrice: ["기준단위 단가"' in table and 'QtyAmount: ["수량 반영 금액"' in table
assert 'sum_price: t("BasePrice"), sum: t("QtyAmount")' in table
assert 'NeedQty: ["수량 입력"' in table
@@ -148,7 +148,7 @@ def test_부품상자만_700줄_공용_안_고침():
assert not re.search(r"""\bel\(\s*["'](button|input|select)["']""", text), name
assert not re.search(r"""createElement\(\s*["'](button|input|select)["']""", text), name
assert "#" not in "".join(re.findall(r"color:\s*[^;]+", text)), name
for name in ("Labor_Rows", "Labor_SangeunModal", "Sangeun_Detail", "Sangeun_Tabs"):
for name in ("Labor_Rows", "Labor_SkEdit", "Sangeun_Detail", "Sangeun_Tabs"):
assert "Hopyo_Imported" not in _src(name), name # 공용 쪽은 새 모달을 모름
+121
View File
@@ -0,0 +1,121 @@
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""PLAN 806-1 — 구조물 조합 일위대가 보기 = 일위대가 컨테이너 부품 한 벌 · 단가산출 줄 = 그 SK 문서 모달(요구 698).
- 화면(소스 지킴) = 조합 상세 · 카드 · 서고 모두 `Labor_Table` 한 부품 · 조합 전용 칸은 `Labor_Combo` · 옛 조합 전용 표 ·
단가산출 보기 모달 걷음 · 단가산출 줄 누름 = `Labor_SkEdit`(수식 탭 부품 · 저장 = 그 SK 문서) · 부품 상자 · 700 줄
- 금액(서버) = 같은 문서 · 같은 SK → 같은 금액(전후 같음) · 모달 [저장] 길(`store.write` sangeun) 뒤엔 그 SK 를 가리킨 줄 금액이
따라 바뀜 · 되돌리면 원래 금액
"""
import re
from decimal import Decimal
from pathlib import Path
import pytest
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Ref as refs
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 world # noqa: F401 — 같은 임시 층
ROOT = Path(__file__).resolve().parents[2]
M02 = ROOT / "M02_MasterTemplete"
PCT = "GC001109" # 원/㎡ · 인력 둘 + 「노무비 * 3 / 100」
COVER = {"덮개": {"labor:LB000036#0": {"qty": 0.5}}}
def _src(name: str) -> str:
return (M02 / name).read_text("utf-8")
def test_조합_상세_카드_서고_한_부품():
detail = _src("M02_MasterTemplete_Labor_Detail.ts")
table = _src("M02_MasterTemplete_Labor_Table.ts")
assert "return mountLaborTable(host, doc, opt);" in detail # 입구 하나
assert "logicTable" not in detail and "afterRowDrag" not in detail # 옛 조합 전용 표 걷음
assert not (
M02 / "M02_MasterTemplete_Labor_SangeunModal.ts"
).exists() # 옛 단가산출 보기 모달 걷음
assert 'opt.mode === "precombo" ? null : createCombo(' in table # 조합 = 컨테이너 + 덧칸
for text in ("openSkEdit({", "specBar(cur,", "specCells(block, cur,", "dg?.draw("):
assert text in table, text
assert "수량: fresh()" in table # 새 줄 수량 — 조합 = 면적을 따름
combo = _src("M02_MasterTemplete_Labor_Combo.ts")
assert "openQtyModal({" in combo and "linkAreaCell(" in combo and "saveTemplate(" in combo
def test_단가산출_줄은_SK_문서_모달_저장은_그_SK():
table = _src("M02_MasterTemplete_Labor_Table.ts")
modal = _src("M02_MasterTemplete_Labor_SkEdit.ts")
assert 'ref.키.startsWith("SK-")' in modal # SK 참조 줄만
assert "if (isSkRef(row.참조) && !opt.calc)" in table # 셈 주입(프로젝트 복사본)엔 옛 줄 모달
assert "readOnly: !edit || row.참조.층 !== opt.layer," in table # 위 층 SK = 읽기만
assert re.search(r'readTemplate\(layer, "sangeun", opt\.ref\.키, null, opt\.ref\.주인\)', modal)
assert re.search(
r'saveTemplate\(\s*layer,\s*"sangeun",\s*opt\.ref\.키', modal
) # 저장 = 그 SK 문서
assert (
"mountSangeunDetail(host, doc," in modal and "source: true" in modal
) # 수식 탭 부품 그대로
assert "await handle.flush();" in modal # 수식 저장 먼저 → 문서 저장
assert "if (!opt.readOnly) head.append(ok);" in modal # 읽기만 = [저장] 없음
assert "if (opened.has(at)) return;" in modal # 빨리 두 번 눌러도 하나
assert "closed: (done) => alive && done && recalc()," in table # 저장했으면 금액 다시 셈
def test_부품상자만_700줄_명사형():
for name in (
"M02_MasterTemplete_Labor_Table.ts",
"M02_MasterTemplete_Labor_Combo.ts",
"M02_MasterTemplete_Labor_SkEdit.ts",
"M02_MasterTemplete_Labor_Detail.ts",
):
text = _src(name)
assert len(text.splitlines()) <= 700, name
assert not re.search(r"""\bel\(\s*["'](button|input|select)["']""", text), name
assert not re.search(r"""createElement\(\s*["'](button|input|select)["']""", text), name
assert "#" not in "".join(re.findall(r"color:\s*[^;]+", text)), name
def _total(layer: Path, sk: str, qty: float) -> Decimal:
"""그 층 일위대가 한 벌(SK 참조 줄 하나 · 수량 qty)의 1 단위 합계."""
with store.at(layer):
ref = {"층": "personal", "주인": refs.here()[1], "키": sk}
doc = labor.clean({"담은로직": [{"로직": "", "참조": ref, "수량": {"손": qty}}]})
return Decimal(str(labor.calc(doc, {})["반영합"]["계"]))
def test_모달_저장_뒤_금액_따라감_되돌리면_같음(world):
"""모달 [저장] = SK 문서 쓰기(`store.write`) — 그 SK 를 가리킨 일위대가 줄 금액이 바뀌고 · 되돌리면 전후 같음."""
personal = world["personal"]
with store.at(personal):
key = refs.create(
"sangeun", {"이름": "sub1 시험", "단위": "㎡", "바탕": {"로직": PCT}, "더한줄": []}
)["이름"]
got = store.read("sangeun", key)
ver0, doc0 = got["판"], got["문서"]
before = _total(personal, key, 2)
assert before == _total(personal, key, 2) and before > 0 # 같은 문서 · 같은 SK = 같은 금액
with store.at(personal):
cut = {**doc0, **COVER}
ver1 = store.write("sangeun", key, ver0, calc.clean_doc(cut, refs.here()))["판"]
changed = _total(personal, key, 2)
assert changed != before # 품 0.5 로 바뀜 — 일위대가 금액이 그 SK 를 따라감
with store.at(personal):
store.write("sangeun", key, ver1, doc0)
assert _total(personal, key, 2) == before # 되돌림 = 전후 같음
def test_옛_판_저장_409(world):
"""모달 [저장]이 낡은 판으로 쓰면 서버가 막음(같은 SK 를 다른 창이 먼저 고침)."""
personal = world["personal"]
with store.at(personal):
key = refs.create(
"sangeun", {"이름": "sub1 시험2", "단위": "㎡", "바탕": {"로직": PCT}, "더한줄": []}
)["이름"]
got = store.read("sangeun", key)
store.write("sangeun", key, got["판"], {**got["문서"], "이름": "다른 창이 고침"})
with pytest.raises(store.StoreError) as why:
store.write("sangeun", key, got["판"], got["문서"])
assert why.value.status == 409
+1 -1
View File
@@ -150,7 +150,7 @@ def test_원문_패널_길_주입() -> None:
assert 'opts.path ?? "/m01/logic/source"' in src
side = (root / "M01_MasterData/M01_MasterData_UI_LogicLab_Side.ts").read_text(encoding="utf-8")
assert "createSourceView()" in side
labor = (root / "M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts").read_text(
labor = (root / "M02_MasterTemplete/M02_MasterTemplete_Labor_Table.ts").read_text(
encoding="utf-8"
)
assert 'path: "/m02/logic-lib/source"' in labor and 'pdf: "/m02/logic-lib/source-pdf"' in labor
@@ -170,10 +170,11 @@ def _src(name):
def test_로직_자재_줄_누름은_그_로직_옵션_모달_사용자_줄은_그대로():
"""148-1 — 고칠 때 · 읽기만 둘 다 `open: openRow` · 바꾸기 단추 칸은 swap 있을 때만 · 사용자 줄 = openMat."""
detail = _src("M02_MasterTemplete_Labor_Detail.ts")
assert "open: openRow,\n swap: swapMat," in detail
assert ": { open: openRow }," in detail
assert "{ open: openMat, remove: removeMat, mark: markMat } : { open: openMat }" in detail
# 806-1 — 조합 · 서고 모두 일위대가표 부품 — 로직 자재 바꾸기 · 세 칸은 줄 모달(`Labor_RowModal`) 안
modal = _src("M02_MasterTemplete_Labor_RowModal.ts")
assert (
"open: () => undefined," in modal and "swap: (_r: number, at: number, key: string)" in modal
)
mats = _src("M02_MasterTemplete_Labor_LogicMats.ts")
assert "const tools = swap" in mats
assert 'tr.addEventListener("click", () => on.open(r))' in mats
+3 -3
View File
@@ -124,9 +124,9 @@ def test_화면_칸_부품_사용자_줄_로직_줄_같은_칸():
rows = _src("M02_MasterTemplete_Labor_MatTable.ts") # 717-3 자재목록 표 나눔
assert "...markCells(mat, on?.mark" in rows and "...markHeads()," in rows
assert "...markCells(" in _src("M02_MasterTemplete_Labor_LogicMats.ts")
detail = _src("M02_MasterTemplete_Labor_Detail.ts")
assert "mark: markLogicMat," in detail and "lib ? undefined" not in detail # 사전조합도(175-1)
assert "mark: markMat" in detail
modal = _src("M02_MasterTemplete_Labor_RowModal.ts") # 806-1 — 조합 · 서고 모두 줄 모달 안
assert "mark: (_r: number, at: number, key: MatMarkKey, v: string)" in modal
assert "mark: (_i, key, v) =>" in modal
# 서버 허용 값 = 화면 허용 값
api = _src("M02_MasterTemplete_Labor_Api.ts")
for key, allowed in labor.MARKS.items():
+1 -1
View File
@@ -83,7 +83,7 @@ def test_소스_보기만_명사형_자리():
words = re.findall(r'"([^"]*[가-힣][^"]*)"', code)
assert words and not [w for w in words if re.search(r"(합니다|해요|한다|하세요)", w)]
assert "createButton" not in code and "fetch" not in code and "sessionStorage" not in code
detail = (M02 / "M02_MasterTemplete_Labor_Detail.ts").read_text(encoding="utf-8")
detail = (M02 / "M02_MasterTemplete_Labor_Table.ts").read_text(encoding="utf-8") # 806-1
assert "...nationalRows(cur.본뜬)" in detail and len(detail.splitlines()) <= 700
api = (M02 / "M02_MasterTemplete_Labor_Api.ts").read_text(encoding="utf-8")
assert "전국자재?: NationalMat[]" in api
+2 -1
View File
@@ -74,8 +74,9 @@ def test_배치_손잡이_차례칸_앞_단추칸():
order = css[css.index(".m02-labrow__order {") :]
assert "flex-direction: column;" in order[: order.index("}")]
assert ".m02-labrow__tools > .ui-btn-group {\n align-items: stretch;" in css
# 806-1 — 조합 상세는 일위대가표(격자 · 우클릭 위로 · 아래로)로 일원화 — 끌기 칸 표(`Labor_Rows`)는 면적산출 패널만
detail = (M02 / "M02_MasterTemplete_Labor_Detail.ts").read_text(encoding="utf-8")
assert "afterRowDrag(logicHost, () =>" in detail
assert "afterRowDrag" not in detail and "logicTable" not in detail
def test_구분열_품셈_글():
+1 -1
View File
@@ -102,7 +102,7 @@ def test_고름에_owner_권한_서고_길에_명시_owner():
assert "owner: next.owner ?? null," in main and "mode, owner: at.owner ?? null" in main
lib = _text("M02_MasterTemplete_Library_List.ts")
assert "getActiveOwner" not in lib and lib.count("opt.owner") >= 6
detail = _text("M02_MasterTemplete_Labor_Detail.ts")
detail = _text("M02_MasterTemplete_Labor_Table.ts") # 806-1
assert "opt.owner !== undefined" in detail
+1 -1
View File
@@ -53,7 +53,7 @@ def test_따라붙기_끔_옵션():
css = PART.with_suffix(".css").read_text(encoding="utf-8")
assert ".ui-side-panels.is-fill {" in css and "overscroll-behavior: contain" in css
m02 = ROOT / "M02_MasterTemplete"
for name in ("M02_MasterTemplete_AreaPanel.ts", "M02_MasterTemplete_Labor_Detail.ts"):
for name in ("M02_MasterTemplete_AreaPanel.ts", "M02_MasterTemplete_Labor_Table.ts"):
assert "follow: false," in (m02 / name).read_text(encoding="utf-8"), name
m01 = (ROOT / "M01_MasterData/M01_MasterData_UI_LogicLab_Side.ts").read_text(encoding="utf-8")
assert "follow" not in m01