fix(M02): 조합 갈라 내기 같은 내용 판정에 차례 · 메모 포함 · 서버 거절 까닭 그대로 알림
- 일위대가 조합: 담은 로직 차례 · 메모까지 같아야 있는 키(원래 키 먼저) — 차례 · 메모만 바꿔도 새 조합 - 자재 조합: 담은 자재 차례까지 비교 - M02 Api_Fetch: detail 문장 · errors 목록 · 입력 검사 목록을 알림 글로(HTTP 422 대신) - 새 조합 자동 이름: 원래 이름 끝 「 (n)」 뗌(겹친 (2) (2) 막음) - test_m02_combo_branch.py 새 규칙으로 고침 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Kyw9qnkJ94pC1A4PfpDtA
This commit is contained in:
@@ -38,15 +38,21 @@ async function call<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
...init,
|
||||
});
|
||||
const body = (await response.json().catch(() => ({}))) as { detail?: unknown } & T;
|
||||
if (response.status === 409)
|
||||
throw new StaleError(typeof body.detail === "string" ? body.detail : "409");
|
||||
if (!response.ok) {
|
||||
const detail = body.detail;
|
||||
throw new Error(typeof detail === "string" ? detail : `HTTP ${response.status}`);
|
||||
}
|
||||
if (response.status === 409) throw new StaleError(reason(body.detail, response.status));
|
||||
if (!response.ok) throw new Error(reason(body.detail, response.status));
|
||||
return body;
|
||||
}
|
||||
|
||||
/** 서버가 막은 까닭 그대로 — 문장 · `{errors: [...]}` · 입력 검사 목록(`[{msg}]`) */
|
||||
function reason(detail: unknown, status: number): string {
|
||||
if (typeof detail === "string") return detail;
|
||||
const list = Array.isArray(detail) ? detail : (detail as { errors?: unknown } | null)?.errors;
|
||||
const lines = Array.isArray(list)
|
||||
? list.map((x) => (typeof x === "string" ? x : String((x as { msg?: unknown })?.msg ?? "")))
|
||||
: [];
|
||||
return lines.filter(Boolean).join(" · ") || `HTTP ${status}`;
|
||||
}
|
||||
|
||||
const enc = encodeURIComponent;
|
||||
const q = (projectId: string | null): string => (projectId ? `?project_id=${enc(projectId)}` : "");
|
||||
const head = (layer: Layer): string =>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
· 일위대가 조합(UA · `일위대가조합.json`) — 새 줄은 M01 조합 저장소가 만듦(부르기만)
|
||||
· 자재 조합(UM · `자재조합.json`) — M01 마스터 저장(`save`)으로 더함(키 = 대장 다음 번호)
|
||||
|
||||
조합은 여러 열 · 모든 프로젝트가 같이 씀 — 제자리 고치지 않음. 담은 것의 집합(차례 · 메모 무시)이
|
||||
이미 있는 조합과 같으면 새로 안 만들고 그 키 · 새 줄의 본뜬키 = 원래 키 · 소유 현장.
|
||||
조합은 여러 열 · 모든 프로젝트가 같이 씀 — 제자리 고치지 않음. 담은 것(차례 · 메모까지)이
|
||||
이미 있는 조합과 같으면 새로 안 만들고 그 키(원래 키 먼저) · 새 줄의 본뜬키 = 원래 키 · 소유 현장.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -41,14 +41,14 @@ def _branch(
|
||||
field: str,
|
||||
file: str,
|
||||
slot: str,
|
||||
ident: str,
|
||||
ident: tuple[str, ...],
|
||||
items: list[dict],
|
||||
source: str,
|
||||
make: Callable[[str, list[dict]], dict],
|
||||
name: str,
|
||||
) -> dict[str, Any]:
|
||||
"""집계표 판 검사 → 같은 내용 찾기 · 없으면 `make(이름, 줄들)` 로 새 줄 → 집계표 그 열 `field`.
|
||||
`slot` = 담은 칸(담은로직 · 담은자재) · `ident` = 담은 줄의 알맹이 칸(로직 · 코드)."""
|
||||
`slot` = 담은 칸(담은로직 · 담은자재) · `ident` = 담은 줄에서 견주는 칸(로직 · 메모 / 코드)."""
|
||||
table = store.read("table", store.STRUCTURE_TABLE)
|
||||
if version and table["판"] != version:
|
||||
raise store.StoreError(409, {"stale": [store.STRUCTURE_TABLE], "판": table["판"]})
|
||||
@@ -58,8 +58,10 @@ def _branch(
|
||||
if not isinstance(items, list) or not items:
|
||||
raise store.StoreError(400, "담은 것이 비었음")
|
||||
|
||||
def body(got) -> frozenset[str]:
|
||||
return frozenset(str(x.get(ident) or "") for x in got or [] if isinstance(x, dict))
|
||||
def body(got) -> tuple: # 차례대로 · 빈 메모 = null
|
||||
return tuple(
|
||||
tuple(str(x.get(k) or "") for k in ident) for x in got or [] if isinstance(x, dict)
|
||||
)
|
||||
|
||||
rows = _m01(m01.read, file)[0].get("줄") or []
|
||||
same = [r for r in rows if body(r.get(slot)) == body(items)]
|
||||
@@ -102,7 +104,16 @@ def branch_combo(
|
||||
|
||||
name = name.strip() or str(was.get("이름") or source)
|
||||
return _branch(
|
||||
column, version, "일위대가", combo_store.FILE, "담은로직", "로직", items, source, make, name
|
||||
column,
|
||||
version,
|
||||
"일위대가",
|
||||
combo_store.FILE,
|
||||
"담은로직",
|
||||
("로직", "메모"),
|
||||
items,
|
||||
source,
|
||||
make,
|
||||
name,
|
||||
)
|
||||
|
||||
|
||||
@@ -147,5 +158,5 @@ def branch_material_combo(
|
||||
default = " ".join(h for h in store._heads(heads) if h) if heads else column
|
||||
name = name.strip() or str(was.get("이름") or default or column)
|
||||
return _branch(
|
||||
column, version, "자재조합", MATERIAL_FILE, "담은자재", "코드", items, source, make, name
|
||||
column, version, "자재조합", MATERIAL_FILE, "담은자재", ("코드",), items, source, make, name
|
||||
)
|
||||
|
||||
@@ -116,8 +116,13 @@ export function askBranch(message: string, defaultName: string): Promise<string
|
||||
});
|
||||
}
|
||||
|
||||
/** 새 조합 이름 자동 — 원래 이름 · 구조물 이름(한쪽이 다른 쪽을 이미 품으면 긴 쪽 하나만) */
|
||||
export const branchName = (original: string, label: string): string =>
|
||||
/** 새 조합 이름 자동 — 원래 이름(끝 「 (n)」 뗌 · 서버가 새로 붙임) · 구조물 이름(한쪽이 다른 쪽을
|
||||
* 이미 품으면 긴 쪽 하나만) */
|
||||
export const branchName = (
|
||||
named: string,
|
||||
label: string,
|
||||
original = named.replace(/ \(\d+\)$/, ""),
|
||||
): string =>
|
||||
!original || label.includes(original)
|
||||
? label || original
|
||||
: !label || original.includes(label)
|
||||
|
||||
Reference in New Issue
Block a user