feat(M02): 일위대가 수량 안 넣은 줄 경고 · 복사 생성 이름 「… 복사」(PLAN 701-4)
- 일위대가 카드 · 상세 합계 줄 — 수량산출 빈 줄 수 「수량 안 넣은 줄 n — 합계에서 빠짐」(좁은 카드에서 제 줄 · 줄바꿈) - 복사 생성(나무 copyIn) = 대상 복사이름 → 새 이름 「원래 이름 복사」 · 또 하면 「… 복사(1)」 · 그냥 붙여넣기는 그대로 - 시험 test_m02_tree_paste(임시 storage) 한 건 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"""여러 항목 · 폴더를 층 사이 붙여넣기(PLAN 30-2) — 구조물 라이브러리 나무의 [복사] → [붙여넣기].
|
||||
|
||||
붙일 폴더(`dest`) 아래 새 항목 — 대상 층 새 키(SC · SP) · 열 `본뜬` = 원본(층 · 주인 · 키) ·
|
||||
같은 이름은 「이름(1)」 · 폴더는 안의 항목 · 빈 폴더까지(폴더 이름이 겹치면 「폴더(1)」).
|
||||
같은 이름은 「이름(1)」 · 폴더는 안의 항목 · 빈 폴더까지(폴더 이름이 겹치면 「폴더(1)」) ·
|
||||
복사 생성(대상 `복사이름`) = 새 이름 「원래 이름 복사」(701-4 · 층이 달라도 같은 이름 둘 안 생김).
|
||||
파일(도면 · 면적 · 입력 연결)은 `Items.copy_item` 이 옮김. 권한은 부르는 쪽 몫.
|
||||
"""
|
||||
|
||||
@@ -73,7 +74,8 @@ def paste(
|
||||
if found is None:
|
||||
raise store.StoreError(404, f"없는 항목 「{key}」")
|
||||
used = [*taken, *(p for _, p in plan)]
|
||||
path = tree.unique_path([*dest, found[-1]], lambda p: p in used)
|
||||
name = f"{found[-1]} 복사" if one.get("복사이름") else found[-1]
|
||||
path = tree.unique_path([*dest, name], lambda p: p in used)
|
||||
renamed += [path[-1]] if path[-1] != found[-1] else []
|
||||
plan.append((key, path))
|
||||
continue
|
||||
|
||||
@@ -467,6 +467,8 @@ export const editTree = (
|
||||
export interface TreeRef {
|
||||
경로: string[];
|
||||
키?: string;
|
||||
/** 복사 생성(701-4) — 새 이름 = 「원래 이름 복사」(같은 이름 둘 안 생김) */
|
||||
복사이름?: boolean;
|
||||
}
|
||||
|
||||
/** 복사해 둔 것 — 나무 종류(`kind` · 구조물 · 일위대가) · 복사한 층 · 고른 것들 */
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
border-top: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* 701-4 수량 안 넣은 줄 경고 — 합계 아래 제 줄 · 좁은 카드에서도 줄바꿈 */
|
||||
.m02-labrow__name > .m02-labrow__noqty {
|
||||
display: block;
|
||||
max-width: 9rem;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.m02-labrow__move {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -493,6 +493,13 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
});
|
||||
const sum = (part: Partial<LaborCost> | undefined, c: (typeof COSTS)[number]): number | null =>
|
||||
part?.[c] ?? null;
|
||||
// 701-4 수량산출 빈 줄 = 반영 금액 없음 → 합계에서 빠짐 — 조용히 빠지지 않게 합계 줄에 경고
|
||||
const noQty = data.calc
|
||||
? data.rows.filter((row) => {
|
||||
const line = lineOf(lineId(row) ?? "");
|
||||
return !!line && (line.수량 === null || line.수량 === undefined);
|
||||
}).length
|
||||
: 0;
|
||||
const foot = data.rows.length
|
||||
? [
|
||||
el("tr", {
|
||||
@@ -500,7 +507,21 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
children: [
|
||||
el("td", { className: "m02-labrow__no", text: "" }),
|
||||
el("td", { className: "m02-labrow__kind", text: "" }),
|
||||
el("td", { className: "m02-labrow__name", text: tc("Prev_Sum") }),
|
||||
el("td", {
|
||||
className: "m02-labrow__name",
|
||||
children: [
|
||||
el("span", { text: tc("Prev_Sum") }),
|
||||
...(noQty
|
||||
? [
|
||||
el("small", {
|
||||
className: "ui-text ui-text--error m02-labrow__noqty",
|
||||
text: tc("Labor_NoQty").replace("{n}", String(noQty)),
|
||||
attrs: { "data-labor-noqty": String(noQty) },
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
el("td", { className: "m02-labrow__alias", text: "" }),
|
||||
el("td", { className: "m02-labrow__qty", text: "" }),
|
||||
el("td", { className: "m02-labrow__unit", text: "" }),
|
||||
|
||||
@@ -96,6 +96,10 @@ const TEXT = {
|
||||
Lib_Personal: ["개인", "Personal"],
|
||||
Labor_AlreadyRef: ["이미 담은 일위대가 조합 — {value}", "Already added — {value}"],
|
||||
Labor_Empty: ["빈 값 {n}", "{n} empty"],
|
||||
Labor_NoQty: [
|
||||
"수량 안 넣은 줄 {n} — 합계에서 빠짐",
|
||||
"{n} rows without quantity — left out of the sum",
|
||||
],
|
||||
Labor_Lost: [
|
||||
"로직 입력이 바뀌어 저장 때 빠지는 값 — {value}",
|
||||
"Inputs no longer in the logic (dropped on save) — {value}",
|
||||
|
||||
@@ -673,7 +673,12 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
|
||||
const run = api.paste;
|
||||
if (!run || !opt.canEdit || !(await ready())) return;
|
||||
await apply(() =>
|
||||
run(opt.layer, owner, { kind: clipKind, from, refs: [{ 경로: [], 키: key }] }, dest),
|
||||
run(
|
||||
opt.layer,
|
||||
owner,
|
||||
{ kind: clipKind, from, refs: [{ 경로: [], 키: key, 복사이름: true }] }, // 701-4 「… 복사」
|
||||
dest,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from resources.tester import test_m02_tree as base
|
||||
@@ -158,3 +159,27 @@ def test_항목_추가_창_카드_격자_표_접기():
|
||||
assert "max-height: calc(100dvh - var(--app-header-h));\n overflow-y: auto;" in css
|
||||
main = (m02 / "M02_MasterTemplete_UI_Main.ts").read_text("utf-8")
|
||||
assert "host.hidden = !host.hidden;" in main and 'title.classList.toggle("is-folded"' in main
|
||||
|
||||
|
||||
def test_복사_생성_이름_원래_이름_복사_같은_이름_둘_없음(world):
|
||||
"""701-4 — 복사 생성(대상 `복사이름`) = 「원래 이름 복사」 · 또 하면 「… 복사(1)」 · 그냥 붙여넣기는 그대로."""
|
||||
company = _company(world)
|
||||
wall = company.item(["옹벽"])["key"]
|
||||
client = _as(world)
|
||||
body = {
|
||||
"from_layer": "company",
|
||||
"대상": [{"경로": [], "키": wall, "복사이름": True}],
|
||||
"경로": [],
|
||||
}
|
||||
first = client.post(PASTE.format("personal"), json=body).json()
|
||||
second = client.post(PASTE.format("personal"), json=body).json()
|
||||
plain = client.post(PASTE.format("personal"), json={**body, "대상": [{"경로": [], "키": wall}]})
|
||||
names = [r["경로"][-1] for r in plain.json()["items"]]
|
||||
assert sorted(names) == ["옹벽", "옹벽 복사", "옹벽 복사(1)"]
|
||||
assert first["알림"]["바뀐이름"] == ["옹벽 복사"] and second["알림"]["바뀐이름"] == [
|
||||
"옹벽 복사(1)"
|
||||
]
|
||||
assert len(set(names)) == len(names) # 같은 이름 둘 없음
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
ui = (root / "M02_MasterTemplete/M02_MasterTemplete_TreePanel.ts").read_text("utf-8")
|
||||
assert "refs: [{ 경로: [], 키: key, 복사이름: true }]" in ui # 복사 생성 길만
|
||||
|
||||
Reference in New Issue
Block a user