From ecd25ae9eeb7db0cd2986e2c8889c51e829d6378 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 18:21:24 +0900 Subject: [PATCH] =?UTF-8?q?fix(b08):=20=ED=94=84=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EB=9E=A8=20=EA=B8=B0=EB=B3=B8=20=EB=B0=9C=ED=96=89=EB=B3=B8?= =?UTF-8?q?=EC=97=94=20=EC=9B=90=EB=AC=B8=20=EA=B3=B5=EC=82=AC=EB=AA=85?= =?UTF-8?q?=C2=B7=ED=8C=8C=EC=9D=BC=EB=AA=85=EC=9D=84=20=EC=95=88=20?= =?UTF-8?q?=EC=8B=A4=EC=9D=8C(=EB=B8=8C=EB=A0=88=EC=9D=B8=20=E2=91=A1)=20?= =?UTF-8?q?=E2=80=94=20=EC=B6=9C=EC=B2=98=EC=97=90=20=E3=80=8C=EC=9B=90?= =?UTF-8?q?=EB=AC=B8=EC=97=90=EC=84=9C=20=EB=BD=91=EC=9D=8C=20=E2=80=94=20?= =?UTF-8?q?=EA=B3=B5=EC=82=AC=EB=AA=85=EC=9D=80=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=8B=9C=20=EA=B0=80=EB=A6=BC=E3=80=8D=20=C2=B7=20=EC=9B=90?= =?UTF-8?q?=EB=B3=B8(=EA=B0=9C=EC=9D=B8=20=EB=8B=A8)=C2=B7=ED=9A=8C?= =?UTF-8?q?=EC=82=AC=20=EB=8B=A8=20=EB=B0=9C=ED=96=89=EC=9D=80=20=EA=B7=B8?= =?UTF-8?q?=EB=8C=80=EB=A1=9C(=E2=91=A2)=20=C2=B7=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=EC=B0=BD=EC=97=90=20=E3=80=8C=EC=9D=B4=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=EC=9D=80=20=E3=80=8C=E2=97=8B=E2=97=8B?= =?UTF-8?q?=E3=80=8D=EC=97=90=EC=84=9C=20=EB=BD=91=EC=9D=80=20=EA=B2=83=20?= =?UTF-8?q?=E2=80=94=20=EA=B3=B5=EC=82=AC=EB=AA=85=EC=9D=80=20=EB=B9=BC?= =?UTF-8?q?=EA=B3=A0=20=EB=B0=9C=ED=96=89=EB=90=A9=EB=8B=88=EB=8B=A4?= =?UTF-8?q?=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 화면(936be972 · 봉화 호표 가져온 장): 확인창 문구 확인 · 공용 storage 라 취소(쓰기 없음) · 가림은 임시 storage 시험 · 되돌림 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq --- .../B08_Quantity_Engine_StructureLibrary.py | 7 ++++ .../B08_Quantity_Engine_StructureTemplate.py | 2 ++ .../B08_Quantity_UI_StructureSheet.ts | 3 ++ .../B08_Quantity_UI_StructureSheet_Library.ts | 12 ++++++- .../tester/test_b08_structure_library.py | 35 +++++++++++++++++++ 5 files changed, 58 insertions(+), 1 deletion(-) diff --git a/B08_Quantity/B08_Quantity_Engine_StructureLibrary.py b/B08_Quantity/B08_Quantity_Engine_StructureLibrary.py index 7878a655..2e730dae 100644 --- a/B08_Quantity/B08_Quantity_Engine_StructureLibrary.py +++ b/B08_Quantity/B08_Quantity_Engine_StructureLibrary.py @@ -28,6 +28,9 @@ LIBRARY_SUBDIR = "library" #: 프로그램 기본 작업본 자리 — 회사 번호 폴더와 안 겹치게 밑줄 이름. PROGRAM_SUBDIR = "_program" +#: 프로그램 기본 발행본에서 뺄 출처 칸 — 어느 공사인지(2026-09-14 브레인 판정 ②). +ORIGIN_MASKED_KEYS = ("project", "file") +ORIGIN_MASKED_NOTE = "원문에서 뽑음 — 공사명은 발행 시 가림" def program_library_dir() -> Path: @@ -194,6 +197,10 @@ def save_personal( for row in overridden_rows(template, overrides) ] item = {k: v for k, v in template.items() if k != "imported_from"} + if tier == "program" and isinstance(item.get("origin"), dict): + # 모든 회사로 가는 발행본 — 원문 공사명·파일명은 안 실음(원본·회사 단은 그대로 · 브레인 ②③). + kept = {k: v for k, v in item["origin"].items() if k not in ORIGIN_MASKED_KEYS} + item["origin"] = {**kept, "masked": ORIGIN_MASKED_NOTE} if unit_price_rows is not None: item["unit_price"] = {**(item.get("unit_price") or {}), "rows": unit_price_rows} _write(folder, {**item, "code": code, "library_tier": tier, "rows": rows}) diff --git a/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py b/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py index 1f8c704f..8e089bd6 100644 --- a/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py +++ b/B08_Quantity/B08_Quantity_Engine_StructureTemplate.py @@ -441,6 +441,8 @@ def apply_templates( "name": template.get("name"), "code": template.get("code"), "imported_from": template.get("imported_from"), + # 발행 확인창이 「○○공사에서 뽑은 것 — 공사명은 빼고 발행」을 보이게(브레인 ②). + "origin_project": (template.get("origin") or {}).get("project"), } sheet["formula_sheet"] = body # 실무 관측값 같은 「대안 후보」 — 값을 바꾸지 않고 칸 옆에 보이기만(판정 Ⓑ). diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet.ts b/B08_Quantity/B08_Quantity_UI_StructureSheet.ts index 72e5c75a..4529fc4b 100644 --- a/B08_Quantity/B08_Quantity_UI_StructureSheet.ts +++ b/B08_Quantity/B08_Quantity_UI_StructureSheet.ts @@ -86,6 +86,8 @@ export interface StructureSheet extends StandardSheetSpec { name: string; code?: string | null; imported_from?: string | null; + /** 뽑아 온 원문 공사명(STmate 고정형) — 프로그램 기본 발행 때 가림. */ + origin_project?: string | null; }; /** 화면이 조작 중 왕복 없이 다시 풀 장 한 벌(L=1, 고친 식 얹힘). */ formula_sheet?: FormulaSheet; @@ -422,6 +424,7 @@ export function renderStructureSheets(projectId: string | null): HTMLElement { sheetKey: sheet.key, typeId: sheet.library_item.type_id, currentCode: sheet.library_item.code ?? null, + originProject: sheet.library_item.origin_project, isDirty: () => dirty, confirmTake: () => { const edited = editedRows(sheet); diff --git a/B08_Quantity/B08_Quantity_UI_StructureSheet_Library.ts b/B08_Quantity/B08_Quantity_UI_StructureSheet_Library.ts index e3bad0ed..8ef3da1e 100644 --- a/B08_Quantity/B08_Quantity_UI_StructureSheet_Library.ts +++ b/B08_Quantity/B08_Quantity_UI_StructureSheet_Library.ts @@ -52,6 +52,8 @@ export interface LibraryPanelOptions { /** 저장 안 한 식이 있으면 [내 라이브러리에 저장]을 막음 — 저장된 식만 개인 단으로 감. */ isDirty: () => boolean; onImported: (notes: string[]) => Promise; + /** 이 장 양식이 뽑아 온 원문 공사명 — 프로그램 기본 발행 확인창에 「빼고 발행」을 알림. */ + originProject?: string | null; } /** 가져오기 · [내 라이브러리에 저장] · [내 것 지우기] 칸. 개인 단 두 단추는 프로젝트를 안 바꿈. */ @@ -243,7 +245,15 @@ export function buildLibraryPanel(options: LibraryPanelOptions): HTMLElement { const button = personal(label, async () => { if (isDirty()) return "저장 안 한 식이 있음 — [식 저장] 먼저"; const whom = tier === "program" ? "모든 회사가 쓰는 프로그램 기본" : "우리 회사 라이브러리"; - if (!window.confirm(`이 장의 양식과 고친 식을 ${whom}에 발행 — 같은 종류가 있으면 덮어씀`)) { + const masked = + tier === "program" && options.originProject + ? `\n이 항목은 「${options.originProject}」에서 뽑은 것 — 공사명은 빼고 발행됩니다` + : ""; + if ( + !window.confirm( + `이 장의 양식과 고친 식을 ${whom}에 발행 — 같은 종류가 있으면 덮어씀${masked}`, + ) + ) { return ""; } await readJson( diff --git a/resources/tester/test_b08_structure_library.py b/resources/tester/test_b08_structure_library.py index ef5af844..8fab215f 100644 --- a/resources/tester/test_b08_structure_library.py +++ b/resources/tester/test_b08_structure_library.py @@ -261,6 +261,41 @@ def test_발행은_권한대로_회사_기본_단에_같은_모양으로(client: ] +def test_프로그램_기본_발행본엔_원문_공사명_파일명을_안_싣는다(tmp_path: Path) -> None: + """2026-09-14 브레인 ② — 모든 회사로 가는 발행본에서만 가림 · 원본·회사 단은 그대로(③).""" + origin = { + "kind": "stmate_xlsx", + "file": "봉화.xlsx", + "project": "2024년 봉화 임도", + "hopyo_no": 6, + } + template = {**load_template("masonry_wet"), "origin": origin} + program = library_module.save_personal(tmp_path / "program", template, None, None, "program") + company = library_module.save_personal(tmp_path / "company", template, None, None, "company") + published = json.loads((tmp_path / "program" / f"{program}.json").read_text(encoding="utf-8")) + kept = json.loads((tmp_path / "company" / f"{company}.json").read_text(encoding="utf-8")) + assert "project" not in published["origin"] and "file" not in published["origin"] + assert published["origin"]["masked"] == "원문에서 뽑음 — 공사명은 발행 시 가림" + assert published["origin"]["kind"] == "stmate_xlsx" + assert kept["origin"] == origin + assert template["origin"] == origin # 원본은 안 건드림 + + +def test_발행_확인창이_가릴_공사명을_보인다() -> None: + ui = (ROOT / "B08_Quantity" / "B08_Quantity_UI_StructureSheet_Library.ts").read_text( + encoding="utf-8" + ) + assert "originProject" in ui and "공사명은 빼고 발행됩니다" in ui + sheet_ui = (ROOT / "B08_Quantity" / "B08_Quantity_UI_StructureSheet.ts").read_text( + encoding="utf-8" + ) + assert "originProject: sheet.library_item.origin_project" in sheet_ui + template_py = (ROOT / "B08_Quantity" / "B08_Quantity_Engine_StructureTemplate.py").read_text( + encoding="utf-8" + ) + assert '"origin_project"' in template_py + + def test_회사_없는_사람은_개인_단을_못_쓴다(client: TestClient) -> None: client.app.dependency_overrides[verify_session] = lambda: {"company_id": None, "user_id": 1} sheet = _sheet(client)