feat(M02): 사전조합 구분 · 상세구분 = 구조물군 › 종류 + 공통 · 옛 품셈 분류 = 미분류(134-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
This commit is contained in:
@@ -147,9 +147,15 @@ def clean_doc(kind: str, doc: Any) -> dict[str, Any]:
|
||||
return out
|
||||
|
||||
|
||||
_OLD_CLASS = re.compile(r"품셈") # 134-5 — 옛 품셈 분류(건설품셈 공통 · 산림품셈 · 07장 …)
|
||||
|
||||
|
||||
def filled(kind: str, doc: Any) -> Any:
|
||||
"""옛 서고 문서(51-12 앞) 읽을 때 칸 채움 — 사전조합 = 빈 글(화면 몫)."""
|
||||
if not isinstance(doc, dict) or kind not in PREFIX or "구분" in doc:
|
||||
"""옛 서고 문서 읽을 때 칸 채움 — 칸 없음(51-12 앞) · 옛 품셈 분류(134-5) = 빈 글(미분류) ·
|
||||
파일은 안 고침([저장] 때 굳음) · 그 밖 지금 값 그대로(구조물정의가 바뀌어도 화면이 선택지로 둠)."""
|
||||
if not isinstance(doc, dict) or kind not in PREFIX:
|
||||
return doc
|
||||
if "구분" in doc and not _OLD_CLASS.search(str(doc.get("구분") or "")):
|
||||
return doc
|
||||
return {**doc, "구분": "", "상세구분": ""}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import { ROUTES } from "@config/config_frontend";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import { setPendingLogicKey } from "../M01_MasterData/M01_MasterData_UI_Side";
|
||||
import { createSourceView } from "../M01_MasterData/M01_MasterData_UI_LogicLab_Source";
|
||||
import type { SubBrief } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
|
||||
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
|
||||
type Recalc = (typeof import("../A00_Common/spreadsheet/spreadsheet_recalc"))["recalcWorkbook"];
|
||||
import {
|
||||
@@ -43,7 +42,6 @@ import { lineId, settle, type LaborRef } from "./M02_MasterTemplete_Labor_Calc";
|
||||
import {
|
||||
areaLinks,
|
||||
calcLabor,
|
||||
fetchLibLogicSubs,
|
||||
fetchLogicInputs,
|
||||
normalizeLabor,
|
||||
type LaborCalc,
|
||||
@@ -68,6 +66,8 @@ import { insideKeys, releaseTied } from "./M02_Labor_LogicMats_Model";
|
||||
import { keepKnown } from "./M02_Labor_Values_Model";
|
||||
import { pickMaterial } from "./M02_Material_Pick";
|
||||
import { createUnitPick, NO_UNIT } from "./DataFlow/M02_DF_UnitPick";
|
||||
import { fetchStructureDef } from "./StructOptions/M02_SO_Api";
|
||||
import { precomboClasses } from "./M02_MasterTemplete_UI_Side_Precombo";
|
||||
import "../M01_MasterData/M01_MasterData_UI_Logic_Style.css";
|
||||
import "../M01_MasterData/M01_MasterData_UI_LogicLab_Style.css";
|
||||
import "@ui/ui_template_general_layout.css";
|
||||
@@ -559,14 +559,15 @@ export function mountLaborDetail(
|
||||
opt.onChanged(dirty());
|
||||
});
|
||||
|
||||
/** 서고 구분 · 상세구분(51-12) — 로직 구분 목록(logic-lib/subs)에서 고름(구분 바꾸면 상세구분 = 그 첫 줄) */
|
||||
/** 서고 구분 · 상세구분(51-12 · 134-5) — 구조물군 › 종류 + 공통(`precomboClasses`)에서 고름 ·
|
||||
* 구분 바꾸면 상세구분 = 그 첫 줄(공통) · 빈 값 = 미분류 */
|
||||
const meta = cur as { 구분?: string | null; 상세구분?: string | null };
|
||||
const libMeta = (): HTMLElement[] => {
|
||||
const sub = createSelectField({ options: [], label: tc("Sub"), disabled: !edit });
|
||||
const detail = createSelectField({ options: [], label: tc("Detail"), disabled: !edit });
|
||||
sub.root.dataset.laborField = "구분";
|
||||
detail.root.dataset.laborField = "상세구분";
|
||||
let subs: SubBrief[] = [];
|
||||
let subs: { name: string; details: string[] }[] = [];
|
||||
// 목록에 없는 지금 값(빈 것 = 「—」 · 옛 문서)도 선택지로 둠
|
||||
const opts = (names: string[], now: string | null | undefined) =>
|
||||
[...new Set([now ?? "", ...names])]
|
||||
@@ -594,9 +595,9 @@ export function mountLaborDetail(
|
||||
opt.onChanged(dirty());
|
||||
});
|
||||
fill();
|
||||
void fetchLibLogicSubs().then(
|
||||
(got) => {
|
||||
subs = got;
|
||||
void fetchStructureDef().then(
|
||||
(def) => {
|
||||
subs = precomboClasses(def);
|
||||
fill();
|
||||
},
|
||||
() => undefined,
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
type LibraryBody,
|
||||
type TemplateInfo,
|
||||
} from "./M02_MasterTemplete_Api_Fetch";
|
||||
import { fetchLibLogicSubs } from "./M02_MasterTemplete_Labor_Api";
|
||||
import { tc } from "./M02_MasterTemplete_Labor_Text";
|
||||
import type { PrecomboKind } from "./M02_MasterTemplete_UI_Side_Precombo";
|
||||
|
||||
@@ -182,17 +181,10 @@ export function mountLibraryList(
|
||||
const create = async (body: LibraryBody, label: string): Promise<void> =>
|
||||
made((await newLibrary(opt.layer, opt.kind, body, opt.owner)).이름, label);
|
||||
|
||||
/** 사전조합 = 이름 + 구분 · 상세구분 처음 값 = 로직 구분 목록 첫 줄(51-12 · 상세에서 바꿈) · 빈 묶음 */
|
||||
/** 사전조합 = 이름 · 구분 · 상세구분 빈 값(미분류 · 134-5 · 상세에서 고름) · 빈 묶음 */
|
||||
btnNew.addEventListener("click", () => {
|
||||
nameDialog(L("M02_NewTitle"), "", async (name) => {
|
||||
const first = (await fetchLibLogicSubs())[0];
|
||||
const 문서 = {
|
||||
이름: name,
|
||||
구분: first?.name ?? null,
|
||||
상세구분: first?.details[0] ?? null,
|
||||
단위: null,
|
||||
담은로직: [],
|
||||
};
|
||||
const 문서 = { 이름: name, 구분: "", 상세구분: "", 단위: null, 담은로직: [] };
|
||||
await create({ 문서 }, name);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { el } from "@ui/ui_template_elements";
|
||||
import { createTreeRow, renderTree, type TreeNode } from "@ui/ui_template_tree";
|
||||
import { listTemplates, type Kind, type Layer } from "./M02_MasterTemplete_Api_Fetch";
|
||||
import type { Root } from "./M02_MasterTemplete_Layer";
|
||||
import type { StructDef } from "./StructOptions/M02_SO_Def_Model";
|
||||
import { tc } from "./M02_MasterTemplete_Labor_Text";
|
||||
import type { Selection } from "./M02_MasterTemplete_UI_Main";
|
||||
|
||||
@@ -18,6 +19,21 @@ export type PrecomboKind = Extract<Kind, "precombo">;
|
||||
export const PRECOMBO_TITLE = "일위대가 사전조합";
|
||||
export const isPrecombo = (kind: Kind): kind is PrecomboKind => kind === "precombo";
|
||||
|
||||
/** 여러 구조물에 쓰는 묶음(콘크리트 · 거푸집 등) — 군마다 첫 상세구분(134-5) */
|
||||
export const COMMON = "공통";
|
||||
|
||||
/** 사전조합 구분 · 상세구분(134-5) — 구조물군 A~G › 그 군 구조물 종류 + 「공통」(원천 `구조물정의.json` ·
|
||||
* B05 구조물 배치와 같은 갈래 · 꺼진 종류도 넣음) */
|
||||
export const precomboClasses = (
|
||||
def: Pick<StructDef, "구조물군" | "types">,
|
||||
): { name: string; details: string[] }[] =>
|
||||
Object.entries(def.구조물군)
|
||||
.filter(([key]) => /^[A-G]$/.test(key))
|
||||
.map(([key, name]) => ({
|
||||
name,
|
||||
details: [COMMON, ...new Set(def.types.filter((t) => t.group === key).map((t) => t.name))],
|
||||
}));
|
||||
|
||||
/** 사전조합 층(134-3) — 페이지 층과 따로 · `owner` null = 자기 층 */
|
||||
export interface PrecomboScope {
|
||||
root: Root;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* M02 일위대가 사전조합 층(134-3) 헬퍼 — `M02_MasterTemplete_Api_Fetch.ts` 명시 owner 길 ·
|
||||
* `M02_MasterTemplete_UI_Side_Precombo.ts` `groupRows`(구분 › 상세구분 2단) 를 트랜스파일해 Node 로 돌림 ·
|
||||
* fetch 대역이 부른 주소를 모음 · stdin = {rows} · stdout = {urls, groups} JSON. */
|
||||
* `precomboClasses`(134-5 · 구조물군 › 종류 + 공통) · fetch 대역이 부른 주소를 모음 ·
|
||||
* stdin = {rows, def?} · stdout = {urls, groups, classes} JSON. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
@@ -48,5 +49,6 @@ global.fetch = async (url) => {
|
||||
구분: g.구분,
|
||||
상세: [...g.상세].map(([k, v]) => [k, v.map((r) => r.이름)]),
|
||||
}));
|
||||
process.stdout.write(JSON.stringify({ urls, groups }));
|
||||
const classes = input.def ? side.precomboClasses(input.def) : null; // 134-5
|
||||
process.stdout.write(JSON.stringify({ urls, groups, classes }));
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
"""일위대가 사전조합 구분 · 상세구분 새 분류(PLAN 134-5) — 구조물군 A~G › 그 군 종류 + 「공통」.
|
||||
|
||||
- 원천 = 마스터 `구조물정의.json`(구조물군 · types) — 군마다 첫 상세구분 「공통」 · 꺼진 종류도 · 「호환(기타)」 뺌
|
||||
- 옛 품셈 분류(건설품셈 공통 · 산림품셈) = 읽을 때 빈 값(미분류) · 파일 안 고침 · 새 분류 값 · 지금 값은 그대로
|
||||
- 새 문서 = 빈 값(미분류) · 상세 드롭다운 원천 = 구조물 정의(로직 구분 목록 아님)
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = Path(HERE).parents[1]
|
||||
M02 = ROOT / "M02_MasterTemplete"
|
||||
|
||||
|
||||
def test_분류는_구조물군_종류_공통():
|
||||
defn = json.loads((ROOT / "resources/master_template/구조물정의.json").read_text("utf-8"))
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_m02_precombo_scope.cjs")],
|
||||
input=json.dumps({"rows": [], "def": defn}, ensure_ascii=False),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
check=True,
|
||||
)
|
||||
classes = json.loads(proc.stdout)["classes"]
|
||||
groups = {k: v for k, v in defn["구조물군"].items() if k in "ABCDEFG"}
|
||||
assert [c["name"] for c in classes] == list(
|
||||
groups.values()
|
||||
) # A 횡단배수 … G 노면공 · 기타 없음
|
||||
c = next(c for c in classes if c["name"] == groups["C"])
|
||||
assert c["details"] == [
|
||||
"공통",
|
||||
"옹벽",
|
||||
"돌쌓기(찰)",
|
||||
"돌쌓기(메)",
|
||||
"흙막이",
|
||||
"큰돌쌓기",
|
||||
"소단",
|
||||
]
|
||||
b = next(c for c in classes if c["name"] == groups["B"])
|
||||
assert "측구(옆도랑)" in b["details"] # 꺼진 종류도 넣음
|
||||
assert all(
|
||||
c["details"][0] == "공통" and len(set(c["details"])) == len(c["details"]) for c in classes
|
||||
)
|
||||
|
||||
|
||||
def test_옛_품셈_분류는_미분류_새_값은_그대로():
|
||||
old = {"이름": "가", "구분": "건설품셈 공통", "상세구분": "07장 돌공사", "담은로직": []}
|
||||
got = refs.filled("precombo", old)
|
||||
assert (got["구분"], got["상세구분"]) == ("", "") and old["구분"] == "건설품셈 공통"
|
||||
assert refs.filled("precombo", {**old, "구분": "산림품셈", "상세구분": "03장"})["구분"] == ""
|
||||
now = {**old, "구분": "C 사면안정", "상세구분": "옹벽"}
|
||||
assert refs.filled("precombo", now) is now
|
||||
gone = {**old, "구분": "Z 없어진 군", "상세구분": "x"} # 지금 값은 선택지로(화면 몫)
|
||||
assert refs.filled("precombo", gone) is gone
|
||||
assert refs.filled("precombo", {"담은로직": []})["구분"] == "" # 칸 없는 옛 문서
|
||||
|
||||
|
||||
def test_화면_원천은_구조물_정의_새_문서는_미분류():
|
||||
detail = (M02 / "M02_MasterTemplete_Labor_Detail.ts").read_text("utf-8")
|
||||
assert "subs = precomboClasses(def);" in detail and "fetchLibLogicSubs" not in detail
|
||||
assert '[...new Set([now ?? "", ...names])]' in detail # 지금 값도 선택지 그대로
|
||||
lib = (M02 / "M02_MasterTemplete_Library_List.ts").read_text("utf-8")
|
||||
assert '구분: "", 상세구분: ""' in lib and "fetchLibLogicSubs" not in lib
|
||||
Reference in New Issue
Block a user