코드 - 고정 줄 단가 = M02_Labor_Fixed.line(사본 묶음 · 이은 노임 · 자재 = 묶음 값) — B09 단가 책으로 다시 안 덮음 - 대가 · 산근 고친 칸 = 품만 실제로(단가 칸 덮기 줄 남던 흠 · 품 고친 뒤 줄 금액 다시 셈 흠 py · ts 짝) - 산근 글 = 엔진 단가 셈 그대로 · 별도계상 재료 줄 끝 「· 별도계상 = 0.0」 · 계 줄 = 블록 합 - 노무비 · 중기목록 이름에서 규격 겹침 뗌 · TS 바탕 셈 이름 PARTS → MACHINE_PARTS(짝 시험이 TS 를 이어 붙일 때 겹침) 시험(고친 까닭 = 단가 원천이 B09 책 → M02 엔진 · 노임 · 기계 덮개 · 단가 고친 칸 · 중기사용료 걷음) - 144 바탕 · 뼈대: 운전사 · 중기사용료 · 경비 · 덮개 시험 걷음 → 노무 · 중기 나열 · 사급 표시 · 자재만 책 - 145-1 · 145-2 · 155 · 157-1 · 166-2 · 166-6 · 719-3 · 719-4 · 723-4 · 778-4 · 792-3: 줄 단가 = own(엔진) · 산근 = Solver() · collect 새 부름 꼴 - 702 · 705-1 · 719-2: 단가 칸 덮개 = 안 읽음 · 고정 줄 = M02 셈(노임 새 판 따라가기 시험 걷음) - 203-1 · 296 · 703-3 · 795-4: 단가 책 = 자재 줄로 - 707 · 707-6 · 707-7: 단가표 불러오기 = 자재만 · 거창 이은 안내판 줄 = M02 사본 셈(엑셀 −50,000) - 201-1: 환율 · 중기사용료 표 걷음 · b09 저장 속도: 산근 앞 풀이 그대로(단가 표 같음 견줌 걷음) - 대상 113 파일 764 통과 · 25 실패 = 원래 실패(formatMoney · formatPrice 없음 · 단위 글 ㎜ · sec · 719-1 · 796-2e · 717-4 · 721 · 311 · 243 · 246) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
135 lines
6.9 KiB
Python
135 lines
6.9 KiB
Python
"""203-1 B09 좌측 「데이터 점검」 묶음(사용자 60번).
|
|
|
|
- 묶음 이름 = 데이터 점검 · 안 설명 글 없음(굳힌 시각 · 까닭 = [업데이트] 공용 호버)
|
|
- [단가 갱신] → [업데이트] · 낡으면(서버 `book_stale` = 지금 M01 로 굳힌 책과 다름) 점 배지(is-pending)
|
|
- 「연결 점검」 탭 내용 = 묶음 안 목록(누르면 그 탭 그 줄) · 위 탭 줄에서 뺌
|
|
- 701-2 첫 화면 = 설계내역서 · 목록 머리 「빈 줄 n」 = 설계내역서 금액 모르는 줄(줄마다 하나 · 까닭 모음) ·
|
|
그 밖(호표 · 재료) = 「그 밖 확인 n곳」(탭 · 키마다 하나)
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
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_Prices as prices # noqa: E402
|
|
from resources.tester.helper_b09_group_cards import TABS, run # noqa: E402
|
|
|
|
CHECK = "B09_Estimation/B09_Estimation_UI_Check.ts"
|
|
SHELL = (ROOT / "B09_Estimation/B09_Estimation_UI_Shell.ts").read_text("utf-8")
|
|
|
|
|
|
def test_묶음_이름_단추_글_설명_없음():
|
|
assert (
|
|
'createPanelCard("내역 점검", true)' in SHELL and "원가 단가" not in SHELL
|
|
) # 269 공용 카드
|
|
assert 'label: "단가 갱신",' in SHELL and 'label: "업데이트"' not in SHELL # 795-4 이름
|
|
# 옛 굳힌 시각 안내 글(made) 없음 · 묶음 = 제목 · [단가 갱신] · 연결 점검
|
|
assert "made.textContent" not in SHELL and "b09-estimation__hint" not in SHELL
|
|
assert "section.append(refresh, check.list);" in SHELL # 제목 = 공용 카드(269)
|
|
assert "check.draw(payload, sheets);" in SHELL
|
|
|
|
|
|
def test_연결_점검_탭_없음_첫_화면_설계내역서():
|
|
out = run(
|
|
[TABS],
|
|
"\nconsole.log(JSON.stringify({ ids: TABS.map(([t]) => t), names: TABS.map(([, n]) => n),"
|
|
" groups: TAB_GROUPS.map(([n]) => n), first: TABS[0][0] }));",
|
|
)
|
|
assert "check" not in out["ids"] and "연결 점검" not in out["names"] + out["groups"]
|
|
assert out["first"] == "cost" and out["groups"][0] == "공사원가계산서" # 엑셀 탭 차례 그대로
|
|
assert 'let current: TabId = "bill";' in SHELL
|
|
assert "buildCheck" not in SHELL and "check:" not in SHELL
|
|
|
|
|
|
def test_좌측_목록_이동_호버_배지():
|
|
out = run(
|
|
[TABS, CHECK],
|
|
"""
|
|
const moves = [];
|
|
const refresh = document.createElement("button");
|
|
const panel = createCheckPanel(refresh, (link) => moves.push(link));
|
|
const sheets = {
|
|
bill: { rows: [
|
|
{ kind: "title", cost: "direct", key: "sec:1", name: "토공사", sum: null },
|
|
{ kind: "item", cost: "direct", key: "earth:3", name: "제근", sum: null },
|
|
{ kind: "item", cost: "direct", key: "earth:4", name: "절토", sum: 100 },
|
|
{ kind: "item", cost: "direct", key: "st:w", name: "옹벽", sum: null },
|
|
{ kind: "item", cost: "owner", key: "mat:1", name: "레미콘", sum: null },
|
|
] },
|
|
gaps: [
|
|
{ tab: "bill", key: "earth:3", name: "제근", why: "수량 연결 없음" },
|
|
{ tab: "bill", key: "earth:3", name: "제근", why: "준비공 대가 — 연결 없음" },
|
|
{ tab: "dg", key: "DG1", name: "찰쌓기", why: "대가 원천 없음" },
|
|
{ tab: "dg", key: "DG1", name: "찰쌓기", why: "대가 원천 없음" },
|
|
{ tab: "material", key: "MT9", name: "모래", why: "단가 없음" },
|
|
],
|
|
};
|
|
const payload = { book: { made: "2026-10-01T09:00:00" }, stale: "M01 에서 바뀐 단가 2",
|
|
notes: ["B08 자재 집계표 못 읽음"], inputs: { missing: ["EQ1", "LB2"] } };
|
|
panel.draw(payload, sheets);
|
|
const view = () => ({
|
|
pending: refresh.classList.contains("is-pending"),
|
|
texts: panel.list.children.map((c) => c.text),
|
|
rows: [1, 3].flatMap((n) =>
|
|
(panel.list.children[n]?.children ?? []).map((r) => [r.dataset.i, r.className, r.text])),
|
|
});
|
|
const a = view();
|
|
const blankBox = panel.list.children[1];
|
|
const rowsBox = panel.list.children[3];
|
|
rowsBox.children[1].click();
|
|
const tipRefresh = hoverCalls.find((h) => h.root === refresh).text();
|
|
const rowHover = hoverCalls.find((h) => h.root === panel.list);
|
|
const tipRow = rowHover.text(rowsBox.children[0]);
|
|
const tipBlank = [0, 1].map((n) => rowHover.text(blankBox.children[n]));
|
|
panel.draw({ book: null, stale: null }, { gaps: [] });
|
|
const b = view();
|
|
const tipEmpty = refreshTip({ book: null, stale: "단가 아직 안 굳힘" });
|
|
console.log(JSON.stringify({ a, b, moves, tipRefresh, tipRow, tipBlank, selector: rowHover.selector,
|
|
tipEmpty }));
|
|
""",
|
|
)
|
|
a, b = out["a"], out["b"]
|
|
assert a["pending"] and not b["pending"]
|
|
# 빈 줄 = 직접공사 항목 중 금액 모름(관급 · 제목 · 아는 줄 빼고) · 같은 줄 까닭은 하나로
|
|
assert a["texts"][0] == "금액 빈 줄 2" and a["texts"][2] == "그 밖 확인 2곳"
|
|
assert a["rows"] == [
|
|
["0", "ui-sidebar-row", "제근설계내역서"],
|
|
["1", "ui-sidebar-row", "옹벽설계내역서"],
|
|
["2", "ui-sidebar-row", "찰쌓기일위대가표"],
|
|
["3", "ui-sidebar-row", "모래재료비목록표"],
|
|
]
|
|
assert out["tipBlank"][0] == "수량 연결 없음 · 준비공 대가 — 연결 없음"
|
|
assert out["tipBlank"][1].startswith("금액 모름") # 까닭 없는 빈 줄(이은 대가에 빈 줄)
|
|
assert a["texts"][4:] == ["B08 자재 집계표 못 읽음", "M01 에 없는 키 2 — EQ1 · LB2"]
|
|
assert out["moves"] == [{"tab": "material", "key": "MT9"}]
|
|
assert out["tipRow"] == "대가 원천 없음" and out["selector"] == "[data-i]"
|
|
assert "단가 굳힘 2026-10-01 09:00:00" in out["tipRefresh"]
|
|
assert "단가 갱신 가능 — M01 에서 바뀐 단가 2" in out["tipRefresh"]
|
|
assert b["texts"] == ["금액 빈 줄 0"] and b["rows"] == []
|
|
assert out["tipEmpty"].startswith("단가 아직 안 굳힘") and "단가 갱신 가능" in out["tipEmpty"]
|
|
|
|
|
|
def test_배지_조건_서버_book_stale():
|
|
keys = ["MT000401", "MT000338", "LB000002"] # 841-1 책 = 자재만(노임 키는 안 셈)
|
|
book = json.loads(json.dumps(prices.refresh_book(keys), ensure_ascii=False))
|
|
assert prices.book_stale(book, keys) is None # 방금 굳힌 책 = 업데이트 필요 없음
|
|
assert prices.book_stale(None, keys) == "단가 아직 안 굳힘"
|
|
first, *rest = list(book["rows"])
|
|
old = {**book, "rows": {k: book["rows"][k] for k in rest}}
|
|
old["rows"][rest[0]] = {**old["rows"][rest[0]], "prices": {}}
|
|
assert prices.book_stale(old, keys) == "새 단가 줄 1 · M01 에서 바뀐 단가 1"
|
|
router = (ROOT / "B09_Estimation/B09_Estimation_Router.py").read_text("utf-8")
|
|
assert '"stale": await asyncio.to_thread(prices.book_stale, book, keys),' in router
|
|
|
|
|
|
def test_배지_꼴_경고_색_토큰():
|
|
css = (ROOT / "B09_Estimation/B09_Estimation_UI_Page.css").read_text("utf-8")
|
|
block = css[css.index(".b09-options .ui-btn.is-pending::after") :]
|
|
block = block[: block.index("}")]
|
|
assert "position: absolute" in block and "border-radius: 50%" in block
|
|
assert "background: var(--color-warning)" in block
|