feat(804-10): 옵션 드롭다운 기계 · 집게 원문번호 = 「이름 규격 (원문번호)」 글(요구 692-2)
- 값(원문번호)은 그대로 · 글만 바꿈 — M02 사본 저장 · 로직 부름 · 엑셀 이음 그대로 - 서버 GET /api/m02/df/machine-names(로그인만) = 기계 원문번호 → 이름 규격 · 브라우저는 한 번 받아 둠 - 수식 탭 옵션(formula_doc_options) · 단가산출 상세 옵션(calc_sheet varRow) 같은 도우미 · M01 · M02 같이 - 목록이 모두 원문번호 꼴일 때만(빈 칸 뺌) · 문서 선택 목록 원문번호 전수 이름 있음 - test_804_10 셋 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import { createInputField, createSelectField } from "@ui/ui_template_input";
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { formatLineMoney, formatMoney } from "../common_util/common_util_money";
|
||||
import { nameMachines, optionText } from "./formula_doc_options";
|
||||
import "./calc_sheet.css";
|
||||
|
||||
export interface SheetLine {
|
||||
@@ -162,16 +163,17 @@ const lineRow = (line: SheetLine, label = line.이름): HTMLElement =>
|
||||
export function varRow(spec: SheetInput, value: string, onInput: (v: string) => void): HTMLElement {
|
||||
let control: HTMLElement;
|
||||
if (spec.고르기?.length) {
|
||||
control = createSelectField({
|
||||
options: [...(value ? [] : [""]), ...spec.고르기.map(String)].map((o) => ({
|
||||
value: o,
|
||||
text: o,
|
||||
})),
|
||||
const list = [...(value ? [] : [""]), ...spec.고르기.map(String)];
|
||||
const items = () => list.map((o) => ({ value: o, text: optionText(o) })); // 기계 번호 = 이름(요구 692-2)
|
||||
const f = createSelectField({
|
||||
options: items(),
|
||||
value,
|
||||
compact: true,
|
||||
ariaLabel: spec.이름,
|
||||
onChange: onInput,
|
||||
}).root;
|
||||
});
|
||||
nameMachines(list, () => f.setOptions(items(), f.select.value));
|
||||
control = f.root;
|
||||
} else {
|
||||
const box = createInputField({ value, compact: true });
|
||||
box.input.setAttribute("aria-label", spec.이름);
|
||||
|
||||
@@ -2,10 +2,29 @@
|
||||
* formula_doc_options.ts
|
||||
* 수식 문서 옵션 가로(PLAN 802-3 · 802-4b-2 · 요구 655 「옵션 선택은 상단에 수평」) — 선택 · 입력 기호마다 칸 하나
|
||||
* 값 = 화면 값(`given`) › 문서 값 · 고침은 부른 쪽 `set`(M01 = 화면 값만 · M02 사본 = 문서 값) · `lock` = 읽기만
|
||||
* 목록이 모두 기계 원문번호(`0101-0007`)면 글 = 「이름 규격 (원문번호)」(요구 692-2 · 값은 그대로 · 이름은 한 번 받아 둠)
|
||||
* ========================================================================== */
|
||||
|
||||
import { createInputField, createSelectField } from "@ui/ui_template_input";
|
||||
import { varId, type DocVar, type FormulaDoc } from "../common_util/common_util_formula_doc";
|
||||
import { cfMachineNames } from "../M02_MasterTemplete/DataFlow/M02_DF_Api";
|
||||
|
||||
const MACHINE = /^\d{4}-\d{4}$/;
|
||||
let names: Record<string, string> = {};
|
||||
|
||||
/** 고르기 글 — 기계 원문번호는 이름 · 규격을 앞에 */
|
||||
export const optionText = (x: string, known: Record<string, string> = names): string =>
|
||||
known[x] ? `${known[x]} (${x})` : x;
|
||||
|
||||
/** 목록이 모두 원문번호(빈 칸 빼고)면 이름을 받아 `redraw`(글만 다시) — 수식 탭 옵션 · 단가산출 상세 옵션 */
|
||||
export function nameMachines(list: string[], redraw: () => void): void {
|
||||
const codes = list.filter(Boolean);
|
||||
if (codes.length && codes.every((x) => MACHINE.test(x)) && codes.some((x) => !(x in names)))
|
||||
void cfMachineNames().then((got) => {
|
||||
names = got;
|
||||
redraw();
|
||||
});
|
||||
}
|
||||
|
||||
export function buildOptions(
|
||||
doc: FormulaDoc | null,
|
||||
@@ -22,9 +41,10 @@ export function buildOptions(
|
||||
const now = k in given ? given[k] : v.연결?.값;
|
||||
if (kind === "선택") {
|
||||
const list = (v.연결?.목록 ?? []).map(String);
|
||||
const items = () => list.map((x) => ({ value: x, text: optionText(x) }));
|
||||
const f = createSelectField({
|
||||
label,
|
||||
options: list.map((x) => ({ value: x, text: x })),
|
||||
options: items(),
|
||||
value: String(now ?? ""),
|
||||
compact: true,
|
||||
size: 28,
|
||||
@@ -32,6 +52,7 @@ export function buildOptions(
|
||||
onChange: (x) => set(k, v, x),
|
||||
});
|
||||
f.root.dataset.option = k;
|
||||
nameMachines(list, () => f.setOptions(items(), f.select.value));
|
||||
out.push(f.root);
|
||||
} else {
|
||||
const f = createInputField({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* =============================================================================
|
||||
* M02_DF_Api.ts
|
||||
* 단위 · 금액 표준 표 읽기 — 서버 `GET /api/m02/df/standards`(`M02_DF_Router.py`)
|
||||
* 부르는 곳 = 단위 고르기(`M02_DF_UnitPick`) · 끝수 표준(`M02_DF_Round`)
|
||||
* 부르는 곳 = 단위 고르기(`M02_DF_UnitPick`) · 끝수 표준(`M02_DF_Round`) · 옵션 기계 이름(`formula_doc_options`)
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
@@ -18,3 +18,13 @@ export async function cfStandards(): Promise<CfStandards> {
|
||||
if (!response.ok) throw new Error(`표준 표 읽기 실패 (${response.status})`);
|
||||
return (await response.json()) as CfStandards;
|
||||
}
|
||||
|
||||
let machineNames: Promise<Record<string, string>> | null = null;
|
||||
|
||||
/** 기계 원문번호 → 「이름 규격」 — 한 번 받아 둠(못 받으면 빈 표 · 다음에 다시) */
|
||||
export function cfMachineNames(): Promise<Record<string, string>> {
|
||||
machineNames ??= fetch(`${API_BASE_URL}/m02/df/machine-names`, { credentials: "include" })
|
||||
.then((r) => (r.ok ? (r.json() as Promise<Record<string, string>>) : Promise.reject()))
|
||||
.catch(() => ((machineNames = null), {}));
|
||||
return machineNames;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""단위 · 금액 표준 표 읽기 — `GET /api/m02/df/standards` → {unit, money, saved}.
|
||||
"""단위 · 금액 표준 표 읽기 — `GET /api/m02/df/standards` → {unit, money, saved} · 기계 이름 `GET /machine-names`.
|
||||
|
||||
부르는 곳 = 단위 고르기(`M02_DF_UnitPick.ts`) · 끝수 표준(`M02_DF_Round.ts`).
|
||||
표 = `_standard/` 파일(git 이 따라감) · 권한 = 로그인만(읽기만 · `main.py` 한 줄 · M02 줄 쌓기 단위 고르기).
|
||||
@@ -19,6 +19,20 @@ router = APIRouter(prefix="/api/m02/df", tags=["M02 DataFlow"])
|
||||
STANDARD = Path(__file__).resolve().parent / "_standard"
|
||||
|
||||
|
||||
@router.get("/machine-names")
|
||||
def get_machine_names() -> dict:
|
||||
"""기계 원문번호 → 「이름 규격」(요구 692-2) — 옵션 드롭다운 라벨만 · 값은 원문번호 그대로 · 겹친 번호는 첫 줄."""
|
||||
out: dict[str, str] = {}
|
||||
for data in store.loaded()[0].values():
|
||||
if data.get("그룹") != "기계":
|
||||
continue
|
||||
for row in data.get(store.items_key(data)) or []:
|
||||
code = str(row.get("원문번호") or "")
|
||||
if code and code not in out:
|
||||
out[code] = f"{row.get('이름') or ''} {row.get('규격') or ''}".strip()
|
||||
return out
|
||||
|
||||
|
||||
@router.get("/standards")
|
||||
def get_standards() -> dict:
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""804-10(요구 692-2) — 옵션 드롭다운 기계 · 집게 등 원문번호 목록 = 「이름 규격 (원문번호)」 글 · 값은 그대로.
|
||||
|
||||
① 서버 `GET /api/m02/df/machine-names` = 기계 원문번호 → 「이름 규격」(로그인만)
|
||||
② 수식 문서 선택 목록 중 모두 원문번호 꼴인 목록 — 번호마다 이름이 있음(1,518 전수)
|
||||
③ 화면 부품 `formula_doc_options.ts` — 글만 바꿈(value = 원문번호) · 이름은 한 번 받아 둠
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from M02_MasterTemplete.DataFlow import M02_DF_Router as df
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
CODE = re.compile(r"^\d{4}-\d{4}$")
|
||||
|
||||
|
||||
def test_기계_이름_표_문서_목록_전수() -> None:
|
||||
names = df.get_machine_names()
|
||||
assert names["0101-0007"] == "불도저(무한궤도) 7ton"
|
||||
lists: list[list[str]] = []
|
||||
for f in (ROOT / "resources/master_data/수식문서").glob("*.json"):
|
||||
for doc in json.loads(f.read_text(encoding="utf-8")).get("줄") or []:
|
||||
for v in doc.get("변수") or []:
|
||||
link = v.get("연결") or {}
|
||||
got = [str(x) for x in link.get("목록") or []]
|
||||
if link.get("갈래") == "선택" and got and all(CODE.match(x) for x in got):
|
||||
lists.append(got)
|
||||
assert len(lists) > 200
|
||||
missing = {x for got in lists for x in got if not names.get(x)}
|
||||
assert not missing, sorted(missing)[:10]
|
||||
|
||||
|
||||
def test_옵션_부품_글만_바꿈() -> None:
|
||||
src = (ROOT / "A00_Common/formula_doc_options.ts").read_text(encoding="utf-8")
|
||||
assert "known[x] ? `${known[x]} (${x})` : x" in src
|
||||
assert "list.map((x) => ({ value: x, text: optionText(x) }))" in src
|
||||
assert "nameMachines(list, () => f.setOptions(items(), f.select.value));" in src
|
||||
api = (ROOT / "M02_MasterTemplete/DataFlow/M02_DF_Api.ts").read_text(encoding="utf-8")
|
||||
assert "machineNames ??= fetch(" in api # 한 번만 받음
|
||||
main = (ROOT / "main.py").read_text(encoding="utf-8")
|
||||
at = main.index("M02_MasterTemplete.DataFlow.M02_DF_Router")
|
||||
assert "dependencies=protected," in main[at : at + 120]
|
||||
|
||||
|
||||
def test_상세_탭_옵션_같은_도우미() -> None:
|
||||
sheet = (ROOT / "A00_Common/calc_sheet.ts").read_text(encoding="utf-8")
|
||||
assert "nameMachines(list, () => f.setOptions(items(), f.select.value));" in sheet
|
||||
assert "text: optionText(o)" in sheet
|
||||
Reference in New Issue
Block a user