Merge remote-tracking branches 'origin/sub_laptop_4' and 'origin/sub_laptop_5' into main_laptop_1
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
* 공용 긋기 방식 칸(PLAN 626-3 · 599-1 · 625-1) — 예정노선(D03) 그리기 칸 · 노드 판 · B05 평면 예정노선 그리기가 같이 씀.
|
||||
* 꼴 = 마디 고름 2 × 2(자유지정 · 길이·기울기 고정 / 길이 고정 · 기울기 고정) + 길이 · 기울기 두 열(기울기 아래 ≈ °) ·
|
||||
* 방식에 안 쓰는 칸은 숨김(막힘 = 권한 없음만) · 설명 글 없음(요구 463-6).
|
||||
* 쓰는 법 = `createDrawKindFields({ value, onChange })` → { root, get, set, setDisabled }.
|
||||
* 쓰는 법 = `createDrawKindFields({ value, onChange })` → { root, get, set, setDisabled, setActive }.
|
||||
* 켬끔 꼴(589-9) = `onActive` 를 주면 고른 방식을 다시 누르면 풀림 · 방식을 고르면 켜짐 — 켬 = 긋기 시작 · 끔 = 긋기 끝
|
||||
* (지금 켬인지는 `setActive` 로 맞춤 · 노드 판처럼 `onActive` 없는 곳은 늘 하나 고름).
|
||||
* ========================================================================== */
|
||||
|
||||
import type { UiSize } from "@ui/ui_template_button";
|
||||
@@ -40,31 +42,39 @@ export interface DrawKindFields {
|
||||
/** 값을 바꿈(onChange 는 안 부름). */
|
||||
set: (value: Partial<DrawKindValue>) => void;
|
||||
setDisabled: (disabled: boolean) => void;
|
||||
/** 켬끔 꼴에서 지금 켬인지 맞춤(onActive 는 안 부름 · 끔 = 모두 풀린 모양). */
|
||||
setActive: (on: boolean) => void;
|
||||
}
|
||||
|
||||
/** 긋기 방식 칸 — 고름 · 길이 · 기울기가 바뀔 때마다 `onChange(지금 값)`(빈 · 틀린 수는 칸 오류만). */
|
||||
export function createDrawKindFields(opts: {
|
||||
value: DrawKindValue;
|
||||
onChange: (value: DrawKindValue) => void;
|
||||
/** 켬끔 꼴 — 방식 고름 = true · 고른 방식 다시 누름 = false. */
|
||||
onActive?: (on: boolean) => void;
|
||||
size?: UiSize;
|
||||
}): DrawKindFields {
|
||||
const size = opts.size ?? 24;
|
||||
let value = { ...opts.value };
|
||||
let disabled = false;
|
||||
let active = !opts.onActive;
|
||||
const changed = (): void => opts.onChange({ ...value });
|
||||
const segment = createSegment({
|
||||
options: (Object.keys(DRAW_KIND_LABELS) as DrawConstraint[]).map((kind) => ({
|
||||
value: kind,
|
||||
label: DRAW_KIND_LABELS[kind],
|
||||
})),
|
||||
value: value.kind,
|
||||
value: active ? value.kind : "",
|
||||
size,
|
||||
columns: 2,
|
||||
clearable: !!opts.onActive,
|
||||
ariaLabel: "긋기 방식",
|
||||
onChange: (next) => {
|
||||
if (next === "") return void ((active = false), opts.onActive?.(false));
|
||||
value.kind = next as DrawConstraint;
|
||||
fieldsState();
|
||||
changed();
|
||||
if (opts.onActive && !active) ((active = true), opts.onActive(true));
|
||||
},
|
||||
});
|
||||
segment.root.dataset.role = "draw-constraint";
|
||||
@@ -130,7 +140,7 @@ export function createDrawKindFields(opts: {
|
||||
get: () => ({ ...value }),
|
||||
set(next) {
|
||||
value = { ...value, ...next };
|
||||
segment.set(value.kind);
|
||||
segment.set(active ? value.kind : "");
|
||||
lengthField.input.value = String(value.lengthM);
|
||||
slopeField.input.value = String(value.slopePct);
|
||||
lengthField.setError(undefined);
|
||||
@@ -142,5 +152,10 @@ export function createDrawKindFields(opts: {
|
||||
disabled = next;
|
||||
fieldsState();
|
||||
},
|
||||
setActive(on) {
|
||||
if (!opts.onActive) return;
|
||||
active = on;
|
||||
segment.set(active ? value.kind : "");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* D03 손 긋기 겹 · 그리기 칸(PLAN 562-3 · 580 · 요구서 389 3장 8~10 · 440 3~7) — 점을 찍어 직선으로 이음.
|
||||
*
|
||||
* 우클릭 메뉴(591-2 · `D03_PlanAnalysis_UI_Points`) = 이 자리에서 시작 — 빈 땅도 새 본선(지점 없이 긋기).
|
||||
* [긋기] 하나 — 첫 누름 자리로 시작: 지점 = 새 본선(측점 No.0 = 긋기 시작한 지점) ·
|
||||
* 긋기 방식 칸 켬 = 긋기 시작 · 끔(고른 방식 다시 누름) = 긋기 끝(589-9) — 첫 누름 자리로 시작: 지점 = 새 본선(측점 No.0 = 긋기 시작한 지점) ·
|
||||
* 선이 이미 놓인 지점 = 그 점의 갈래 · 선 꼬리 = 이어 긋기 · 갈래 없는 선 머리 = 뒤집어 이어 긋고 끝낼 때
|
||||
* 되돌림 · 선 위 · 꺾임점 = 갈래. 다른 지점 · 다른 선 둘레(SNAP_PX)에 오면 거기 붙고 그 점에서 끝 —
|
||||
* 머리가 다른 선 꼬리에 닿으면 한 선으로 잇고 · 꺾임점에 닿으면 그 점의 갈래 · 본선 꼬리가 다른 구역에
|
||||
@@ -160,12 +160,10 @@ export function createPlanDraw(params: PlanDrawParams): PlanDraw {
|
||||
const kindFields = createDrawKindFields({
|
||||
value: { kind, lengthM, slopePct },
|
||||
onChange: (value) => ({ kind, lengthM, slopePct } = value),
|
||||
// 방식을 켬 = 긋기 시작 · 고른 방식을 다시 눌러 끔 = 긋기 끝(589-9) · 긋는 중 다른 방식 = 방식만 바뀜
|
||||
onActive: (on) => on !== (mode === "draw") && setMode(on ? "draw" : "idle"),
|
||||
});
|
||||
const drawPanel = createDrawPanel({
|
||||
kind: kindFields,
|
||||
page: params.page,
|
||||
onDraw: () => setMode(mode === "draw" ? "idle" : "draw"),
|
||||
});
|
||||
const drawPanel = createDrawPanel({ kind: kindFields, page: params.page });
|
||||
const panel = drawPanel.root;
|
||||
|
||||
function panelState(): void {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* =============================================================================
|
||||
* D03_PlanAnalysis_UI_DrawPanel.ts
|
||||
* D03 좌측 「그리기」 칸 몸(PLAN 599-1 · 599-4 · 요구 463-10 · 22) — [긋기] · 구간 나눔 마커 · 긋기 방식 칸.
|
||||
* D03 좌측 「그리기」 칸 몸(PLAN 599-1 · 599-4 · 요구 463-10 · 22) — 긋기 방식 칸(켬 = 긋기 시작 · 끔 = 긋기 끝) · 구간 나눔 마커.
|
||||
*
|
||||
* 방식 칸 `createDrawKindFields` = 공용 `A00_Common/map/map_draw_kind`(626-3) — 그리기 칸 · 노드 판(598-4) ·
|
||||
* B05 평면 예정노선 그리기가 같은 부품을 씀(여기서 다시 내보냄).
|
||||
@@ -27,23 +27,16 @@ export {
|
||||
|
||||
export interface DrawPanel {
|
||||
root: HTMLElement;
|
||||
/** 단추 글 · 막힘 — 긋는 중 · 고칠 수 있음 · 마커 놓을 선 있음. */
|
||||
/** 방식 칸 켬 맞춤 · 막힘 — 긋는 중 · 고칠 수 있음 · 마커 놓을 선 있음. */
|
||||
state: (state: { drawing: boolean; editable: boolean; canMark: boolean }) => void;
|
||||
}
|
||||
|
||||
/** 좌측 「그리기」 칸 — [긋기] · 구간 나눔 마커(끌기) · 방식 칸. */
|
||||
/** 좌측 「그리기」 칸 — 방식 칸(켬끔 꼴) · 구간 나눔 마커(끌기). */
|
||||
export function createDrawPanel(opts: {
|
||||
kind: DrawKindFields;
|
||||
onDraw: () => void;
|
||||
/** 기능 표 페이지 — 구간 나눔 마커 단추 = 「years」 사용일 때만. */
|
||||
page: PlanPage;
|
||||
}): DrawPanel {
|
||||
const drawButton = createButton({
|
||||
label: "긋기",
|
||||
variant: "filled",
|
||||
size: 28,
|
||||
onClick: opts.onDraw,
|
||||
});
|
||||
const markButton = createButton({
|
||||
label: "구간 나눔",
|
||||
variant: "ghost",
|
||||
@@ -60,16 +53,16 @@ export function createDrawPanel(opts: {
|
||||
});
|
||||
const row = document.createElement("div");
|
||||
row.className = "d03-plan__row";
|
||||
row.append(drawButton, markButton);
|
||||
row.append(markButton);
|
||||
row.hidden = markButton.hidden; // 마커 단추 없는 쪽(B05)은 줄도 없음
|
||||
const root = document.createElement("div");
|
||||
root.className = "d03-plan__draw";
|
||||
root.append(row, opts.kind.root);
|
||||
root.append(opts.kind.root, row);
|
||||
return {
|
||||
root,
|
||||
state({ drawing, editable, canMark }) {
|
||||
drawButton.querySelector(".ui-btn__label")!.textContent = drawing ? "긋기 끝" : "긋기";
|
||||
drawButton.disabled = !editable;
|
||||
drawButton.setAttribute("aria-pressed", String(drawing));
|
||||
opts.kind.setActive(drawing);
|
||||
opts.kind.setDisabled(!editable);
|
||||
markButton.disabled = !editable || drawing || !canMark;
|
||||
root.dataset.mode = drawing ? "draw" : "idle";
|
||||
},
|
||||
|
||||
@@ -220,7 +220,7 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
|
||||
label: "평면 도구",
|
||||
hint: {
|
||||
lines: [
|
||||
"[긋기] 뒤 지점 · 선 끝 · 선 위 누름 = 시작",
|
||||
"긋기 방식을 켠 뒤 지점 · 선 끝 · 선 위 누름 = 시작",
|
||||
"누름 = 점 찍기 · 다른 구역에 닿으면 그 점의 갈래로 끝",
|
||||
"Enter = 긋기 끝",
|
||||
"Esc = 긋기 취소",
|
||||
|
||||
@@ -658,6 +658,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
|
||||
)
|
||||
sums = {k: cut(row, v, "계") for k, v in sums.items()} # 비목 합마다 끊고 계는 그 합
|
||||
mb.tag(master, row, lines, env) # 줄마다 인력 · 재료 · 경비 · 하위 묶음(712-1)
|
||||
mg.tag(master, row, lines, env) # 옵션으로 빠진 줄 `옵션꺼짐`(753-1)
|
||||
return {"줄": lines, **sums, "계": sum(sums.values()), "중간": _shown(env)}
|
||||
|
||||
|
||||
@@ -678,6 +679,7 @@ from master_material import ( # noqa: E402, F401
|
||||
)
|
||||
import master_pick as mp # noqa: E402 — 검색어 · 고름 · 단위
|
||||
import master_bundle as mb # noqa: E402 — 인력 · 재료 · 경비 묶음
|
||||
import master_gate as mg # noqa: E402 — 옵션으로 빠진 줄
|
||||
from master_formula_check import ( # noqa: E402, F401 — 검사는 나눈 파일 · 이름은 여기서 그대로
|
||||
_check_cut,
|
||||
_check_node,
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""옵션으로 빠진 줄 — **셈 때 파생**(정본에 칸 없음 · PLAN 753-1 · 요구 588-4).
|
||||
|
||||
엔진 `run` 호표 줄 덧칸 `옵션꺼짐` = True — 그 줄 수량 0 이 조건 `만약` 의 간 갈래(상수 0) 탓
|
||||
(폼타이 12.7 · 9.5 · 7.9 셋 중 고른 규격 아닌 줄 · 폼타이 「아님」 이면 셋 다 ·
|
||||
타설길이 문턱으로 고른 장비 아닌 줄). 화면이 숨김(753-2).
|
||||
셈 · 금액 · 줄 차례 그대로 · 덧칸만.
|
||||
|
||||
가름(0 을 낸 마디를 거슬러 감):
|
||||
- `만약` = 간 갈래가 상수 0 → 꺼짐(조건 = 고르기 · 수 입력 문턱 · 중간값 모두) ·
|
||||
아니면 간 갈래 안으로.
|
||||
- 곱 = 0 인 인수 중 하나라도 꺼짐 · 나눗셈 = 분자 · 더하기 · 빼기 = 두 쪽 다 0 이고 하나라도 꺼짐 ·
|
||||
끝수 함수 = 첫 인수 · 최소 · 최대 = 0 인 인수 중 꺼짐 · 중간값 이름 = 그 식으로.
|
||||
- 입력 값 · 표 값 · 요소 · 로직 부름 · 원문 0 = 진짜 0(안 꺼짐 · 구멍땜 개소 0 · 표 칸 0 등).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
import master_formula as mf
|
||||
|
||||
KEY = "옵션꺼짐"
|
||||
_ERRORS = (mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError, RecursionError)
|
||||
_SOURCES = ("name", "find", "field", "ref", "logic") # 값이 입력 · 표에서 오는 마디
|
||||
|
||||
|
||||
class _Trace:
|
||||
def __init__(self, master, row: dict, env: dict):
|
||||
self.master, self.env = master, env
|
||||
self.steps = {s["이름"]: s["식"] for s in row.get("중간") or []}
|
||||
self.memo: dict[str, bool] = {}
|
||||
|
||||
def zero(self, node) -> bool:
|
||||
value = mf.evaluate(node, self.env, self.master)
|
||||
return not isinstance(value, bool) and value == 0
|
||||
|
||||
def off(self, node, depth: int = 0) -> bool:
|
||||
"""이 마디 값 0 이 옵션 갈래 탓인지."""
|
||||
kind = node[0]
|
||||
if depth > 40:
|
||||
return False
|
||||
if kind == "name":
|
||||
name = node[1]
|
||||
if name not in self.steps:
|
||||
return False
|
||||
if name not in self.memo:
|
||||
self.memo[name] = False # 돌고 도는 이름 막음
|
||||
self.memo[name] = self.off(mf.parse(self.steps[name]), depth + 1)
|
||||
return self.memo[name]
|
||||
if kind == "neg":
|
||||
return self.off(node[1], depth + 1)
|
||||
if kind == "bin":
|
||||
sides = node[2:4]
|
||||
if node[1] == "*":
|
||||
return any(self.zero(x) and self.off(x, depth + 1) for x in sides)
|
||||
if node[1] == "/":
|
||||
return self.off(node[2], depth + 1)
|
||||
if node[1] in ("+", "-"):
|
||||
both = all(self.zero(x) for x in sides)
|
||||
return both and any(self.off(x, depth + 1) for x in sides)
|
||||
return False
|
||||
if kind != "call" or not node[2]:
|
||||
return False
|
||||
items = node[2]
|
||||
if node[1] == "만약":
|
||||
taken = items[1] if mf.evaluate(items[0], self.env, self.master) else items[2]
|
||||
fixed = not any(x[0] in _SOURCES for x in mf.walk(taken))
|
||||
if fixed and self.zero(taken):
|
||||
return True
|
||||
return self.off(taken, depth + 1)
|
||||
if node[1] in ("최소", "최대"):
|
||||
return any(self.zero(x) and self.off(x, depth + 1) for x in items)
|
||||
return self.off(items[0], depth + 1)
|
||||
|
||||
|
||||
def tag(master, row: dict, lines: list[dict], env: dict) -> list[dict]:
|
||||
"""엔진 `run` 호표 줄(덧줄 앞)에 `옵션꺼짐` — 수량 0 인 줄만 가름 · 못 가름 = 안 붙임."""
|
||||
trace = None
|
||||
for item, line in zip(row.get("호표") or [], lines):
|
||||
qty = line.get("수량")
|
||||
if not isinstance(qty, Decimal) or qty != 0:
|
||||
continue
|
||||
trace = trace or _Trace(master, row, env)
|
||||
try:
|
||||
if trace.off(mf.parse(item["수량"])):
|
||||
line[KEY] = True
|
||||
except _ERRORS:
|
||||
continue
|
||||
return lines
|
||||
@@ -8,6 +8,7 @@
|
||||
- `묶음` · `장비` = 712-1 `master_bundle`(엔진 줄 덧칸 그대로).
|
||||
- `펼침`(장비 줄만 · 714-1b) = 부른 기계경비 로직의 계산서 줄(조종원 · 주연료 · 잡재료비 · 기계손료 …) ·
|
||||
시간당 비목 소계 · 계 · 부른 키 · 인자 — 셈 결과를 그대로 실음(환산 규칙을 여기 두지 않음).
|
||||
- `옵션꺼짐` = 엔진 줄 덧칸 그대로(753-1 `master_gate` · 옵션 갈래로 0 인 줄 · 화면이 숨김).
|
||||
- `기준단위` = 그 줄 수량 식이 찾는 표의 기준(「10㎡」 · 712-3 `master_basis.basis`) · 답 머리
|
||||
`기준단위` = 로직 결과단위 분모(「10㎡」 · 여럿이면 「㎡ · ㎥」).
|
||||
- `환산근거` = 표 기준배 ↔ 결과단위 배(「10㎡당 → 1㎡당 ÷10」) · 재료 품목 단위 ↔ 줄 단위
|
||||
@@ -24,10 +25,11 @@ from decimal import Decimal
|
||||
import master_basis as mbs
|
||||
import master_bundle as mb
|
||||
import master_formula as mf
|
||||
import master_gate as mg
|
||||
import master_text as mtx
|
||||
import master_unit as mu
|
||||
|
||||
KEYS = ("쓰는입력", "묶음", "장비", "기준단위", "환산근거", "펼침")
|
||||
KEYS = ("쓰는입력", "묶음", "장비", "기준단위", "환산근거", "펼침", "옵션꺼짐")
|
||||
_HOLE = re.compile(r"\{([^{}]+)\}")
|
||||
_ERRORS = (mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError)
|
||||
|
||||
@@ -180,6 +182,8 @@ def annotate(files: dict, key: str, given: dict, answer: dict) -> dict:
|
||||
parts = [item.get("비목")]
|
||||
exprs = [item.get("수량"), item.get("요소")]
|
||||
got = _fields(master, row, exprs, env, per, line, mb.bundle(master, item, env))
|
||||
if line.get(mg.KEY): # 옵션으로 빠진 줄(753-1 · 엔진 줄 덧칸 그대로)
|
||||
got[mg.KEY] = True
|
||||
if got.get("장비"):
|
||||
got["펼침"] = _spread(files, master, item, env)
|
||||
for part in parts:
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
"""옵션으로 빠진 줄 표시(PLAN 753-1 · 요구 588-4) — 엔진 줄 덧칸 `옵션꺼짐`.
|
||||
|
||||
M01 셈(`/calc`) · 계산서(`/text` · `master_sheet_line.annotate`) · SK 셈(`M02_Sangeun_Calc.run`)이
|
||||
같은 엔진 줄을 씀 · 금액 그대로 · 진짜 0(입력 0 · 표 칸 0)은 표시 없음.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "master_data/scripts"))
|
||||
|
||||
import check_master as cm # noqa: E402
|
||||
import master_formula as mf # noqa: E402
|
||||
import master_gate as mg # noqa: E402
|
||||
import master_sheet_line as msl # noqa: E402
|
||||
import master_text as mtx # noqa: E402
|
||||
import pytest # noqa: E402
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01 # noqa: E402
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor # noqa: E402
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc # noqa: E402
|
||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd # noqa: E402
|
||||
|
||||
FORM = "GC000211" # 6-3-1 합판거푸집 — 폼타이 12.7 · 9.5 · 7.9 규격마다 줄
|
||||
TIES = ("폼타이 12.7㎜", "폼타이 9.5㎜", "폼타이 7.9㎜")
|
||||
CASES = [
|
||||
({"폼타이": "아님"}, set(TIES)),
|
||||
({"폼타이": "사용", "폼타이규격": "12.7㎜"}, {"폼타이 9.5㎜", "폼타이 7.9㎜"}),
|
||||
({"폼타이": "사용", "폼타이규격": "9.5㎜"}, {"폼타이 12.7㎜", "폼타이 7.9㎜"}),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def master():
|
||||
files = cm.load()
|
||||
return files, mf.Master(files)
|
||||
|
||||
|
||||
def _given(whole, key, **over):
|
||||
return {**cm.시험입력(whole.logic(key)), **over}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("over,off", CASES)
|
||||
def test_engine_marks_unpicked_ties(master, over, off):
|
||||
_, whole = master
|
||||
got = mf.run(whole, FORM, _given(whole, FORM, **over))
|
||||
marked = {x["이름"] for x in got["줄"] if x.get(mg.KEY)}
|
||||
assert marked == off
|
||||
|
||||
|
||||
def test_real_zero_not_marked(master):
|
||||
_, whole = master
|
||||
got = mf.run(whole, FORM, _given(whole, FORM, 구멍땜=0))
|
||||
holes = [x for x in got["줄"] if "구멍땜" in x["이름"]]
|
||||
assert holes and all(x["수량"] == 0 and not x.get(mg.KEY) for x in holes)
|
||||
# 조건이 참이고 표 칸이 0 인 줄(GC000480 측량초급기능사) — 진짜 0
|
||||
got = mf.run(whole, "GC000480", cm.시험입력(whole.logic("GC000480")))
|
||||
line = got["줄"][3]
|
||||
assert line["수량"] == 0 and not line.get(mg.KEY)
|
||||
|
||||
|
||||
def test_numeric_threshold_marks_other_machine(master):
|
||||
_, whole = master
|
||||
got = mf.run(whole, "GC000187", cm.시험입력(whole.logic("GC000187")))
|
||||
pairs = [(got["줄"][n], got["줄"][n + 1]) for n in (3, 5, 7)]
|
||||
for a, b in pairs: # 타설길이 문턱으로 둘 중 하나 — 0 인 쪽만 표시
|
||||
assert {bool(a.get(mg.KEY)), bool(b.get(mg.KEY))} == {True, False}
|
||||
assert all(bool(x.get(mg.KEY)) == (x["수량"] == 0) for x in (a, b))
|
||||
|
||||
|
||||
def test_money_same_without_mark(master, monkeypatch):
|
||||
_, whole = master
|
||||
for over, _ in CASES:
|
||||
given = _given(whole, FORM, **over)
|
||||
on = mf.run(whole, FORM, given)
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(mg, "tag", lambda *a: a[2])
|
||||
off = mf.run(whole, FORM, given)
|
||||
assert on["계"] == off["계"]
|
||||
assert [x["금액"] for x in on["줄"]] == [x["금액"] for x in off["줄"]]
|
||||
|
||||
|
||||
def test_never_marks_nonzero(master):
|
||||
_, whole = master
|
||||
for key in ("GC000211", "GC000002", "GC000216", "GC000563", "GC000187"):
|
||||
got = mf.run(whole, key, cm.시험입력(whole.logic(key)))
|
||||
assert all(x["수량"] == 0 for x in got["줄"] if x.get(mg.KEY))
|
||||
|
||||
|
||||
def test_sheet_text_carries_mark(master):
|
||||
files, whole = master
|
||||
given = _given(whole, FORM, 폼타이="사용", 폼타이규격="9.5㎜")
|
||||
got = msl.annotate(files, FORM, given, mtx.lines(files, FORM, given))
|
||||
lines = {x["이름"]: x for g in got["groups"] for x in g["줄"]}
|
||||
assert lines["폼타이 9.5㎜"].get(mg.KEY) is None
|
||||
assert lines["폼타이 12.7㎜"][mg.KEY] is True and lines["폼타이 7.9㎜"][mg.KEY] is True
|
||||
assert mg.KEY in msl.KEYS # M02 `/logic-lib/sheet` 도 같은 칸
|
||||
|
||||
|
||||
def test_sk_calc_carries_mark():
|
||||
whole = m01.loaded()[1]
|
||||
doc = {"키": "SK-000001", "이름": "시험", "단위": "㎡", "바탕": {"로직": FORM}}
|
||||
clean = sd.clean(doc, None, whole)
|
||||
got = labor._json(calc.run(clean, whole, {"폼타이": "사용", "폼타이규격": "12.7㎜"}))
|
||||
marked = {x["cid"] for x in got["줄"] if x.get(mg.KEY)}
|
||||
assert marked == {"material:폼타이 9.5㎜#0", "material:폼타이 7.9㎜#0"}
|
||||
base = labor._json(calc.run(clean, whole))
|
||||
assert base["기준"]["계"] == pytest.approx(got["기준"]["계"])
|
||||
@@ -0,0 +1,31 @@
|
||||
"""757-1 (요구 589-9) — 그리기 칸 [긋기] 단추 걷음 · 방식 넷 켬끔 = 긋기 시작 · 끝(D03 · B05 같은 칸).
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_757_1_draw_kind_toggle.py -q
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
read = lambda *parts: ROOT.joinpath(*parts).read_text(encoding="utf-8") # noqa: E731
|
||||
|
||||
|
||||
def test_분할_고르기_풂_꼴() -> None:
|
||||
src = read("ui_template", "ui_template_segment.ts")
|
||||
assert "clearable?: boolean" in src and 'opts.onChange?.("")' in src
|
||||
|
||||
|
||||
def test_방식_칸_켬끔_꼴() -> None:
|
||||
src = read("A00_Common", "map", "map_draw_kind.ts")
|
||||
assert "onActive?: (on: boolean) => void" in src and "setActive" in src
|
||||
assert "clearable: !!opts.onActive" in src # onActive 없는 곳(노드 판)은 늘 하나 고름
|
||||
props = read("D03_PlanAnalysis", "D03_PlanAnalysis_UI_Props.ts")
|
||||
assert "onActive" not in props
|
||||
|
||||
|
||||
def test_긋기_단추_없고_방식_칸이_시작_끝() -> None:
|
||||
panel = read("D03_PlanAnalysis", "D03_PlanAnalysis_UI_DrawPanel.ts")
|
||||
assert "onDraw:" not in panel and '"긋기 끝"' not in panel and 'label: "긋기"' not in panel
|
||||
assert "opts.kind.setActive(drawing)" in panel
|
||||
draw = read("D03_PlanAnalysis", "D03_PlanAnalysis_UI_Draw.ts")
|
||||
assert 'on !== (mode === "draw") && setMode(on ? "draw" : "idle")' in draw
|
||||
assert "onDraw:" not in draw
|
||||
@@ -5,6 +5,7 @@
|
||||
* 꼴 = 글 · 아이콘(+ title) · 글 + 아이콘
|
||||
* 크기 = data-size 20 · 24 · 28 · 32(기본 28)
|
||||
* 줄 = 한 줄(기본) · `columns: 2` = 2 × 2 처럼 여러 줄 칸(긴 이름 넷 · 599-1 D03 긋기 방식)
|
||||
* 풂 = `clearable: true` — 고른 칸을 다시 누르면 모두 풀림(`onChange("")` · 켬끔 고르기 · 589-9 긋기 방식 = 켜면 긋기 시작)
|
||||
* 상태 = 마우스 `:hover` · 눌림 `:active` · 막힘 `disabled` · 고름 = aria-checked — CSS 는 `[data-state]` 로도 받음
|
||||
* 쓰기 = `createSegment({ options: [{ value: "list", label: "목록" }, { value: "card", label: "카드" }], value: "list", onChange })`
|
||||
* 대신 = B01 `.b01-sys-seg` · `.b01-home__seg` · `.b01-proj-seg` · B04 `b04-surface__sheet-method` · B05 `b05-routerec__pills` ·
|
||||
@@ -40,6 +41,8 @@ export function createSegment(opts: {
|
||||
ariaLabel?: string;
|
||||
/** 한 줄 칸 수 — 넘으면 다음 줄(없음 = 한 줄) */
|
||||
columns?: number;
|
||||
/** 고른 칸을 다시 누르면 풂 — `onChange("")` */
|
||||
clearable?: boolean;
|
||||
}): SegmentHandle {
|
||||
const root = document.createElement("div");
|
||||
root.className = "ui-segment";
|
||||
@@ -63,7 +66,11 @@ export function createSegment(opts: {
|
||||
if (o.title && !o.label) b.setAttribute("aria-label", o.title);
|
||||
b.disabled = o.disabled ?? false;
|
||||
b.addEventListener("click", () => {
|
||||
if (current === o.value) return;
|
||||
if (current === o.value) {
|
||||
if (!opts.clearable) return;
|
||||
set("");
|
||||
return opts.onChange?.("");
|
||||
}
|
||||
set(o.value);
|
||||
opts.onChange?.(o.value);
|
||||
});
|
||||
|
||||
@@ -274,7 +274,14 @@ function segmentDemo(s: KitState): HTMLElement | null {
|
||||
const items = [...seg.root.children] as HTMLButtonElement[];
|
||||
if (s === "disabled") items.forEach((b) => markState(b, s));
|
||||
else markState(items[1], s);
|
||||
return seg.root;
|
||||
// 풂 꼴(589-9) — 고른 칸을 다시 누르면 모두 풀림
|
||||
const clearable = createSegment({
|
||||
options: [{ value: "a", label: "방식 가" }, { value: "b", label: "방식 나" }],
|
||||
value: "a",
|
||||
clearable: true,
|
||||
});
|
||||
if (s === "disabled") [...clearable.root.children].forEach((b) => markState(b as HTMLElement, s));
|
||||
return row(seg.root, clearable.root);
|
||||
}
|
||||
function tabsDemo(s: KitState): HTMLElement | null {
|
||||
if (s === "active" || s === "error") return null;
|
||||
|
||||
Reference in New Issue
Block a user