diff --git a/M01_MasterData/M01_MasterData_Store_Combo.py b/M01_MasterData/M01_MasterData_Store_Combo.py index a9ff64d51..d9c18b699 100644 --- a/M01_MasterData/M01_MasterData_Store_Combo.py +++ b/M01_MasterData/M01_MasterData_Store_Combo.py @@ -52,6 +52,9 @@ def _clean(combo: dict, owner: str, key: str | None = None) -> dict: raise store.StoreError(400, "조합 줄에 「이름」 이 없거나 글자 아님") if owner not in OWNERS: raise store.StoreError(400, f"모르는 소유 「{owner}」 — {' · '.join(OWNERS)}") + origin = combo.get("본뜬키") + if origin is not None and (origin == key or origin not in _keys()): + raise store.StoreError(400, f"본뜬키 「{origin}」 — 비었거나 있는 다른 조합 키여야 함") rest = {k: v for k, v in combo.items() if k not in ("키", "원문번호", "소유")} one = {**({"키": key} if key else {}), "소유": owner, **rest} one.setdefault("출처", "자체") diff --git a/M01_MasterData/M01_MasterData_UI_Combo.ts b/M01_MasterData/M01_MasterData_UI_Combo.ts index 9eb017373..4d992d942 100644 --- a/M01_MasterData/M01_MasterData_UI_Combo.ts +++ b/M01_MasterData/M01_MasterData_UI_Combo.ts @@ -39,6 +39,7 @@ const blankCombo = (): Combo => ({ 담은로직: [], 출처: "자체", 소유: "현장", + 본뜬키: null, 비고: null, }); @@ -162,14 +163,21 @@ export function mountM01Combo( tc("Detail"), tc("Head_Unit"), tc("Head_Count"), + tc("Head_Origin"), ]; const rows = shown.map((x) => { const tr = el("tr", { className: "m01-master__table-row", attrs: { "data-combo-key": x.키 }, - children: [x.키, x.이름, x.구분 ?? "", x.상세구분 ?? "", x.단위 ?? "", String(x.count)].map( - (v) => el("td", { text: v }), - ), + children: [ + x.키, + x.이름, + x.구분 ?? "", + x.상세구분 ?? "", + x.단위 ?? "", + String(x.count), + x.본뜬키 ?? tc("Origin_Self"), + ].map((v) => el("td", { text: v })), }); tr.addEventListener("click", () => void open(x.키)); return tr; @@ -369,6 +377,11 @@ export function mountM01Combo( field(tc("Detail"), "상세구분"), field(tc("Head_Unit"), "단위"), field(tc("Head_Note"), "비고"), + el("p", { + className: "m01-logic__muted", + text: `${tc("Head_Origin")}: ${combo.본뜬키 ?? tc("Origin_Self")}`, + attrs: { "data-combo-origin": combo.본뜬키 ?? "" }, + }), ], }), ], diff --git a/M01_MasterData/M01_MasterData_UI_Combo_Api.ts b/M01_MasterData/M01_MasterData_UI_Combo_Api.ts index c0ea8a02b..9a9bd4553 100644 --- a/M01_MasterData/M01_MasterData_UI_Combo_Api.ts +++ b/M01_MasterData/M01_MasterData_UI_Combo_Api.ts @@ -22,6 +22,8 @@ export interface Combo { 담은로직: ComboLogic[]; 출처: string | null; 소유: string | null; + /** 이 조합을 본떠 만든 원본 키 — 비면 원본(M02 가 갈라 낸 조합에 채워짐) */ + 본뜬키: string | null; 비고: string | null; } export interface ComboSummary extends Combo { diff --git a/M01_MasterData/M01_MasterData_UI_Combo_Text.ts b/M01_MasterData/M01_MasterData_UI_Combo_Text.ts index 4456b20fe..0e2dcefc7 100644 --- a/M01_MasterData/M01_MasterData_UI_Combo_Text.ts +++ b/M01_MasterData/M01_MasterData_UI_Combo_Text.ts @@ -30,6 +30,8 @@ const TEXT = { Head_Unit: ["단위", "Unit"], Head_Note: ["비고", "Note"], Head_Count: ["로직 수", "Logics"], + Head_Origin: ["본뜬키", "Copied from"], + Origin_Self: ["원본", "Original"], Logics_Title: ["담은 로직", "Logics in it"], Col_Order: ["차례", "No."], Col_Key: ["로직 키", "Logic key"], diff --git a/resources/master_data/_틀.md b/resources/master_data/_틀.md index b8ed909bc..5041b02e4 100644 --- a/resources/master_data/_틀.md +++ b/resources/master_data/_틀.md @@ -281,13 +281,15 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": null } ] } ``` -- 열 한 벌 — `키` · `이름` · `구분` · `상세구분` · `단위` · `담은로직` · `출처` · `소유` · `비고`(차례까지 이대로 · 안 채운 칸은 `null`). +- 열 한 벌 — `키` · `이름` · `구분` · `상세구분` · `단위` · `담은로직` · `출처` · `소유` · `본뜬키` · `비고`(차례까지 이대로 · 안 채운 칸은 `null`). +- `본뜬키` = 이 조합을 본떠 만든 원본 조합 키(같은 파일의 다른 UA) · `null` 이면 원본 · 자기 키 안 됨 · M02 가 열 몫으로 갈라 낼 때 채움. - **담은 로직 목록만 둠** — 수량 · 비율 · 계산 칸을 두지 않음. 로직마다 하는 일이 달라 수량은 로직마다 따로 들어가고, 그 대입과 합계는 프로젝트 몫(B08 · B09). - `담은로직` 줄 = `{로직, 메모}` 뿐 · 목록 차례가 곧 조합 안 차례. `메모` = 글자 또는 `null` — 수량 같은 값을 넣는 자리가 아님. - `키` = `UA` + 6자리 — 대장 `_키대장.json` 의 `UA` 다음 번호(서버가 줌). 원문이 없어 **줄에 `원문번호` 를 두지 않음**(대장 열쇠는 조합 이름). diff --git a/resources/master_data/_화면_계약.md b/resources/master_data/_화면_계약.md index a6bf8e638..2199db1d3 100644 --- a/resources/master_data/_화면_계약.md +++ b/resources/master_data/_화면_계약.md @@ -183,6 +183,9 @@ | `POST /combo/delete` | `{key, version}` | `{file, version}` | | `POST /combo/preview` | `{combo, inputs}` | `{키, 이름, 단위, 줄: […], 노무비, 재료비, 경비, 계, 멈춤}` | +- `본뜬키` = 본뜬 원본 조합 키(같은 파일의 다른 UA) 또는 `null`(원본) — 없는 키 · 자기 키는 400 · 목록 · 상세에 그대로 나옴. +- `POST /combo/delete` 는 구조물집계표 · `구조물집계표조합` 의 「일위대가」 칸으로 쓰는 열이 있으면 409 `{detail: {errors: […], users: [구조물 이름…]}}` — 지우지 않음. +- 조합 알약 `GET /combo/{키}/inputs` = `{키, 이름, 물량: [{키 = 「이름|단위」, 이름, 단위, 로직: […], 배?}], 조건: []}` — 같은 이름 · 단위 물량은 한 알약. - `combo` = 조합 한 줄 통째(`_틀.md` 10장) · `키`·`소유` 는 보내도 서버 것이 이김 · 안 준 칸은 `null` 로 채워 열 한 벌을 맞춤. - `version` = `GET /combo` 가 준 그 파일 판본 — 다르면 409 `{detail: {stale: ["일위대가조합.json"]}}`. - `GET /combo` 의 `줄` = 담은 로직에 이름을 붙인 목록 `{차례, 로직, 메모, ok, 이름, 원문번호, 구분, 상세구분, 결과단위, 출처}` — 없는 키는 `ok` false 와 `까닭`. diff --git a/resources/master_data/scripts/check_master.py b/resources/master_data/scripts/check_master.py index 264b402ac..2083a0e8b 100644 --- a/resources/master_data/scripts/check_master.py +++ b/resources/master_data/scripts/check_master.py @@ -226,6 +226,7 @@ def check_combo_form(name: str, data: dict) -> list[str]: if not isinstance(rows_, list): return out + [f"{name} · 머리 「줄」 목록 없음"] seen_names: dict[str, str] = {} + own = {str(r.get("키")) for r in rows_} for row in rows_: key = str(row.get("키")) where = f"{name} · {key} {row.get('이름') or ''}".strip() @@ -249,6 +250,9 @@ def check_combo_form(name: str, data: dict) -> list[str]: out.append(f"{where} · 소유 「{row.get('소유')}」 — {' · '.join(mcb.OWNERS)} 아님") if not _source_ok(row.get("출처")): out.append(f"{where} · 출처 모양 「{row.get('출처')}」") + origin = row.get("본뜬키") + if origin is not None and (origin == key or origin not in own): + out.append(f"{where} · 본뜬키 「{origin}」 — 비었거나 이 파일의 다른 조합 키여야 함") return out diff --git a/resources/master_data/scripts/master_combo.py b/resources/master_data/scripts/master_combo.py index 54d6fa2f1..74f1b048c 100644 --- a/resources/master_data/scripts/master_combo.py +++ b/resources/master_data/scripts/master_combo.py @@ -19,7 +19,7 @@ TID = "UA" OWNERS = ("현장", "공용") MAX_이름 = 60 # 조합 이름 최대 글자 수 — 관리자가 짓는 짧은 이름이라 이보다 길면 저장 막음 # 줄 열 한 벌 — 차례까지 이대로(`_틀.md` 10장) -COLS = ("키", "이름", "구분", "상세구분", "단위", "담은로직", "출처", "소유", "비고") +COLS = ("키", "이름", "구분", "상세구분", "단위", "담은로직", "출처", "소유", "본뜬키", "비고") ITEM_COLS = ("로직", "메모") diff --git a/resources/master_data/scripts/master_keys.py b/resources/master_data/scripts/master_keys.py index 10f08fbf9..1b8e1d33d 100644 --- a/resources/master_data/scripts/master_keys.py +++ b/resources/master_data/scripts/master_keys.py @@ -183,6 +183,7 @@ ROW_ORDER = { "담은로직", "출처", "소유", + "본뜬키", "비고", ), } diff --git a/resources/master_data/일위대가조합.json b/resources/master_data/일위대가조합.json index 6aee5be4e..b4c38de24 100644 --- a/resources/master_data/일위대가조합.json +++ b/resources/master_data/일위대가조합.json @@ -12,6 +12,7 @@ "담은로직": [{ "로직": "GF000219", "메모": "메쌓기 — 돌쌓기 인력" }, { "로직": "GF000119", "메모": "노체포설 — 굴착기 0.7㎥" }, { "로직": "GF000137", "메모": "운반 — 중기운반 트레일러(20ton)" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "브레인이 넣은 견본 · 조합 화면을 바로 볼 수 있게 함 · 지워도 됨" }, { @@ -30,6 +31,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "글자새김 자당 6*6 — 우리 로직에 없음\n잔토처리 굴삭기 0.2m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00005 · 울진대흥 B01224" }, { @@ -51,6 +53,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "깬잡석채집 L=30cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n잔토처리 굴삭기 0.7m3 — 우리 로직에 없음\n측구수로관부설 500∼700kg미만 — 우리 로직에 없음\n기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n실무 호표 — 봉화 B00008 · 봉화 B00030 · 영월 B02019 · 영월 B02052 · 영월 B02134" }, { @@ -74,6 +77,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "깬잡석채집 L=45cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채집 기계(0.7㎥,100%) — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채집 기계 — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 굴삭기0.7m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채우기 150mm내외 — 후보 「자갈고르기」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00010 · 봉화 B00011 · 봉화 B00022 · 봉화 B00047 · 울진대흥 B01212 · 울진대흥 B01213 · 울진대흥 B01214 · 울진대흥 B01215 · 울진대흥 B01216 · 울진대흥 B01225 · 울진대흥 B01226 · 울진대흥 B01227 · 울진대흥 B01231 · 울진소광 B02122" }, { @@ -97,6 +101,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "PE관 부설 및 접합 D50 — 후보 「부단수 할정자관 부설 및 접합」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n실무 호표 — 봉화 B00012 · 영월 B02002 · 거창 제12호표" }, { @@ -113,6 +118,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 봉화 B00014 · 울진대흥 B01232" }, { @@ -124,6 +130,7 @@ "담은로직": [{ "로직": "GF000162", "메모": "콘크리트포장(인력) T=20㎝" }, { "로직": "GC000211", "메모": "합판거푸집(6회) 간단" }, { "로직": "GF000163", "메모": "콘크리트포장절단공 임도 (실무 수는 옛 판 값)" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n실무 호표 — 영월 B02059" }, { @@ -150,6 +157,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "막자갈채집 기계(0.7㎥,100%) — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n깬잡석채집 L=30cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n야면석 채집 운반 (25x25x35㎝)장비 — 후보 「야면석 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집운반 4.5톤덤프(L=500) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n큰돌채집운반(뒷길이 0.6~0.8m) 장비(80%) + 인력(20%) — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n녹생토(종자배합) 암절개지 및 특수지반 녹화용 (700kg/㎥) — 우리 로직에 없음\n막자갈채집 기계 — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 굴삭기0.7m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈(파쇄석)채집 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채우기 150mm내외 — 후보 「자갈고르기」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00017 · 봉화 B00018 · 봉화 B00029 · 봉화 B00048 · 봉화 B00049 · 봉화 B00055 · 봉화 B00056 · 영월 B01981 · 영월 B01983 · 영월 B01988 · 영월 B02045 · 영월 B02155 · 울진대흥 B01219 · 울진대흥 B01220 · 울진소광 B02120" }, { @@ -170,6 +178,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "깬잡석채집 L=30cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n잔토처리 굴삭기 0.7m3 — 우리 로직에 없음\n깬잡석채집 L=60cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n파쇄석 채집 L3=35cm내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 굴삭기0.2m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00020 · 봉화 B00032 · 봉화 B00036 · 봉화 B00064 · 울진대흥 B01222 · 울진대흥 B01223 · 울진대흥 B01229 · 울진대흥 B01230 · 울진소광 B02125" }, { @@ -191,6 +200,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "잔토처리 굴삭기 0.2m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00025" }, { @@ -209,6 +219,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "깬잡석채집 L=35cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n야면석 채집 운반 (25x25x35㎝)장비 — 후보 「야면석 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집운반 4.5톤덤프(L=500) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n야면석소운반(L=35CM) (L=20m) — 우리 로직에 없음\n콘크리트소운반 (L=20m) — 우리 로직에 없음\n고임돌채집 굴삭기0.7m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00034 · 영월 B02016 · 영월 B02017 · 울진소광 B02124" }, { @@ -225,6 +236,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 봉화 B00038" }, { @@ -242,6 +254,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 봉화 B00041" }, { @@ -261,6 +274,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 봉화 B00058 · 거창 제5호표 · 거창 제6호표" }, { @@ -277,6 +291,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 봉화 B00062 · 거창 제9호표" }, { @@ -293,6 +308,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "잔토처리 인력100% — 우리 로직에 없음\n실무 호표 — 영월 B00704" }, { @@ -310,6 +326,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n잔토처리 인력100% — 우리 로직에 없음\n실무 호표 — 영월 B01982" }, { @@ -327,6 +344,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n잔토처리 인력100% — 우리 로직에 없음\n실무 호표 — 영월 B01997" }, { @@ -343,6 +361,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n잔토처리 인력100% — 우리 로직에 없음\n실무 호표 — 영월 B02152" }, { @@ -354,6 +373,7 @@ "담은로직": [{ "로직": "GC000149", "메모": "관목식재(단식) H=0.3~0.7m" }, { "로직": "GC000148", "메모": "굴취(관목) H=0.3~0.7m" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "인력운반 기타 — 우리 로직에 없음\n실무 호표 — 계류보전 B01099" }, { @@ -370,6 +390,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 울진소광 B02121" }, { @@ -381,6 +402,7 @@ "담은로직": [{ "로직": "GC000198", "메모": "레미콘타설 무근(장비)" }, { "로직": "GC000214", "메모": "유로폼 설치 및 해체 간단" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 울진소광 B02129" }, { @@ -396,6 +418,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "고임돌채집 굴삭기0.7m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채우기 150mm내외 — 후보 「자갈고르기」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 울진소광 B02123" }, { @@ -415,6 +438,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "물구멍설치 D=50mm — 우리 로직에 없음\n실무 호표 — 울진소광 B02128" }, { @@ -431,6 +455,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 거창 제7호표 · 거창 제8호표" }, { @@ -442,6 +467,7 @@ "담은로직": [{ "로직": "GC000088", "메모": "구조물 터파기(토사) 굴삭기 0.2m3" }, { "로직": "GF000113", "메모": "구조물 되메우기 굴삭기 0.2m3 (실무 수는 옛 판 값)" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 봉화 B00007" }, { @@ -453,6 +479,7 @@ "담은로직": [{ "로직": "GC000088", "메모": "구조물 터파기(토사) 굴삭기 0.2m3" }, { "로직": "GF000113", "메모": "구조물 되메우기 굴삭기 0.2m3 (실무 수는 옛 판 값)" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "PE관 부설 및 접합 D50 — 후보 「부단수 할정자관 부설 및 접합」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n실무 호표 — 봉화 B00039" }, { @@ -464,6 +491,7 @@ "담은로직": [{ "로직": "GC000143", "메모": "평떼붙임 (실무 수는 옛 판 값)" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "단끊기 — 우리 로직에 없음\n단끊기 절취없음 — 우리 로직에 없음\n떼 하차비 — 우리 로직에 없음\n실무 호표 — 봉화 B00052 · 울진소광 B02130" }, { @@ -475,6 +503,7 @@ "담은로직": [{ "로직": "GF000101", "메모": "구조물터파기 토사(B/H 90%+인력10%)" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "잔토처리 토사(B/H 100%) — 우리 로직에 없음\n실무 호표 — 영월 B01963" }, { @@ -486,6 +515,7 @@ "담은로직": [{ "로직": "GF000218", "메모": "잡석운반및다짐 (실무 수는 옛 판 값)" }, { "로직": "GF000124", "메모": "면고르기" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 영월 B01984" }, { @@ -497,6 +527,7 @@ "담은로직": [{ "로직": "GF000218", "메모": "뒷채움돌채집운반부설 4.5톤 덤프트럭(L=500m) (실무 수는 옛 판 값)" }, { "로직": "GF000226", "메모": "큰돌메쌓기 뒷길이 0.6~0.8m" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "고임돌채집운반 4.5톤덤프(L=500) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n큰돌채집운반(뒷길이 0.6~0.8m) 장비(80%) + 인력(20%) — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n녹생토(종자배합) 암절개지 및 특수지반 녹화용 (700kg/㎥) — 우리 로직에 없음\n실무 호표 — 영월 B02133" }, { @@ -508,6 +539,7 @@ "담은로직": [{ "로직": "GF000218", "메모": "뒷채움돌채집운반부설 4.5톤 덤프트럭(L=500m) (실무 수는 옛 판 값)" }, { "로직": "GF000226", "메모": "큰돌메쌓기 뒷길이 0.6~0.8m" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "큰돌채집운반(뒷길이 0.6~0.8m) 장비(80%) + 인력(20%) — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집운반 4.5톤덤프(L=500) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n녹생토(종자배합) 암절개지 및 특수지반 녹화용 (700kg/㎥) — 우리 로직에 없음\n실무 호표 — 영월 B02156" }, { @@ -519,6 +551,7 @@ "담은로직": [{ "로직": "GF000101", "메모": "구조물터파기(토사) 굴삭기0.7m3 (실무 수는 옛 판 값)" }, { "로직": "GC000105", "메모": "다지기 굴삭기0.7m3 (실무 수는 옛 판 값)" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 울진대흥 B01221" }, { @@ -535,6 +568,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 울진대흥 B01234" }, { @@ -546,6 +580,7 @@ "담은로직": [{ "로직": "GC000206", "메모": "철근(현장)가공 Type-Ⅰ-----아님 (실무 수는 옛 판 값)" }, { "로직": "GC000207", "메모": "철근(현장)조립 Type-Ⅰ-----아님" }], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 울진소광 B00287" }, { @@ -561,6 +596,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 울진소광 B02119" }, { @@ -576,6 +612,7 @@ ], "출처": "자체", "소유": "현장", + "본뜬키": null, "비고": "실무 호표 — 거창 제13호표" } ] diff --git a/resources/tester/test_m01_combo_inputs.py b/resources/tester/test_m01_combo_inputs.py index 2ae1a2bcf..d4f174f9f 100644 --- a/resources/tester/test_m01_combo_inputs.py +++ b/resources/tester/test_m01_combo_inputs.py @@ -126,3 +126,18 @@ def test_안_쓰는_조합은_지워진다(own) -> None: own.post("/api/m01/combo/delete", json={"key": key, "version": version}).status_code == 200 ) assert own.get(f"/api/m01/combo?key={key}").status_code == 404 + + +def test_본뜬키는_목록에_나오고_없는_키_자기_키는_400(own) -> None: + base = _new(own, "원본", ["GF000222"]) + combo = {"이름": "본뜬 것", "본뜬키": base, "담은로직": [{"로직": "GF000222", "메모": None}]} + got = own.post("/api/m01/combo/new", json={"combo": combo}).json() + assert got["combo"]["본뜬키"] == base + rows = {r["키"]: r for r in own.get("/api/m01/combos").json()["combos"]} + assert rows[got["key"]]["본뜬키"] == base and rows[base]["본뜬키"] is None + bad = {**combo, "이름": "없는 원본", "본뜬키": "UA999999"} + assert own.post("/api/m01/combo/new", json={"combo": bad}).status_code == 400 + version = own.get(f"/api/m01/combo?key={base}").json()["version"] + me = {"이름": "원본", "본뜬키": base, "담은로직": combo["담은로직"]} + res = own.post("/api/m01/combo/edit", json={"key": base, "version": version, "combo": me}) + assert res.status_code == 400