From 75bbeee0a68cba3201593a36f6f036e270b6266f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 8 Nov 2026 21:09:26 +0900 Subject: [PATCH] =?UTF-8?q?feat(811-1):=20=EA=B0=99=EC=9D=80=20=EC=B8=B5?= =?UTF-8?q?=20SK=20=EC=82=AC=EB=B3=B8=20=EC=95=88=20=EB=A7=8C=EB=93=A6=20?= =?UTF-8?q?=C2=B7=20=EC=A1=B0=ED=95=A9=20=EB=8B=A4=EC=9D=B4=EC=96=B4?= =?UTF-8?q?=EA=B7=B8=EB=9E=A8=20=EC=B0=B8=EC=A1=B0=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=C2=B7=20=EC=98=AE=EA=B8=B0=EA=B8=B0=20=EB=AA=A9=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Labor_SkPick — 주인 null(자기 층) = 로그인 사용자 층 주인으로 견줌 · 같은 층 SK 고름 = 그대로 참조(화면 확인 중 사본 생기던 흠) - Labor_Combo — 다이어그램에 SK 참조 줄 카드(이름 · 값 · 누름 = 그 줄 모달) · 옮긴 뒤 다이어그램이 비지 않게 - 옮기기 보기 목록 _목록_811-1_일위대가참조.xlsx — 층 문서 26 · 로직 줄 172 · 같음 172 · 새 SK 130 · 다름 0 Co-Authored-By: Claude Opus 5.5 --- .../M02_MasterTemplete_Labor_Combo.ts | 31 ++++++++++++++++-- .../M02_MasterTemplete_Labor_SkPick.ts | 19 +++++++---- .../요구_1104/_목록_811-1_일위대가참조.xlsx | Bin 0 -> 7336 bytes resources/tester/test_811_1_labor_ref.py | 7 +++- 4 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 docs/raw/요구_1104/_목록_811-1_일위대가참조.xlsx diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Combo.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Combo.ts index 3aeb99b34..0d661fd49 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Combo.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Combo.ts @@ -29,7 +29,10 @@ import { buildLaborDiagram, diagramTitle, renderComboDiagram, + type DiagramLogic, + type DiagramOut, } from "./M02_MasterTemplete_Labor_Diagram"; +import { lineId } from "./M02_MasterTemplete_Labor_Calc"; import { openQtyModal } from "./M02_MasterTemplete_Labor_QtyModal"; import { qtyShort } from "./M02_MasterTemplete_Labor_Rows"; import { tc } from "./M02_MasterTemplete_Labor_Text"; @@ -155,6 +158,26 @@ export function createCombo(opt: ComboOptions): Combo { }; } +/** 참조 줄 카드(811-1 · 단가산출 = SK 참조 한 행) — 이름 · 값 = 셈 답 · 넣을 값 없음 · 누름 = 그 줄 모달 */ +function refCards(doc: LaborDoc, calc: LaborCalc | null): DiagramLogic[] { + return doc.담은로직.flatMap((r) => { + if (!r.참조) return []; + const line = calc?.줄.find((l) => lineId(l) === lineId(r)); + const money = line?.반영 ?? line?.기준; + const out: DiagramOut = line?.까닭 ? { 멈춤: line.까닭 } : money ? { ...money } : null; + return [ + { + key: r.참조.키, // 같은 참조 두 번 금지 = 줄 하나 + name: r.별칭 || line?.이름 || r.참조.키, + unit: line?.분모 ?? "", + inputs: [], + out, + ...(money ? { basis: line?.반영 ? "반영" : "기준" } : {}), + }, + ]; + }); +} + /** 다이어그램 상자(열 상세) — `draw` 를 표가 다시 그릴 때마다 부름 */ export function diagramBox(): { root: HTMLElement; @@ -179,18 +202,22 @@ export function diagramBox(): { again = () => { if (!root.open) return; const data = buildLaborDiagram({ - rows: doc.담은로직.filter((r) => !r.참조), // ponytail: 참조 줄은 다이어그램에 안 그림 — 그릴 때 카드 꼴 더함 + rows: doc.담은로직.filter((r) => !r.참조), logics: [...logics.values()], calc, mats: doc.자재.length, }); + const at = data.logics.findIndex((l) => l.material); + data.logics.splice(at < 0 ? data.logics.length : at, 0, ...refCards(doc, calc)); renderComboDiagram(host, data, { focusInput: (key) => { const i = doc.담은로직.findIndex((r) => r.로직 === key); if (i >= 0) focus(i); }, - // 로직 카드 → 단가산출 로직 화면(떠날 때 물음은 UI_Main 의 leave guard) + // 로직 카드 → 단가산출 로직 화면(떠날 때 물음은 UI_Main 의 leave guard) · 참조 카드(811-1) = 그 줄 모달 openLogic: (key) => { + const ref = doc.담은로직.findIndex((r) => r.참조?.키 === key); + if (ref >= 0) return focus(ref); setPendingLogicKey(key); navigateTo(ROUTES.M01_MASTER_DATA); }, diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_SkPick.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_SkPick.ts index 506ee436a..83434c33f 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_SkPick.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_SkPick.ts @@ -6,6 +6,7 @@ * · 답 = 넣을 참조 {층, 주인, 키} · 취소 = null(일위대가 문서는 화면 [저장] 때 바뀜) * ========================================================================== */ +import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch"; import { call, newLibrary, type Layer } from "./M02_MasterTemplete_Api_Fetch"; import type { LaborRef } from "./M02_MasterTemplete_Labor_Calc"; import { withSameAsk } from "./M02_MasterTemplete_Library_SameAsk"; @@ -29,15 +30,19 @@ export const skFromLogic = async (s: SkPickScope, code: string): Promise { + if (s.layer === "system" || s.owner) return s.layer === "system" ? null : s.owner; + const me = await fetchSessionUser(); + if (!me) throw new Error("로그인이 끊김"); + return s.layer === "company" ? String(me.company_id) : `${me.company_id}/${me.id}`; +} + /** 고른 SK — 이 층 것이면 그대로 · 다른 층이면 이 층 사본 */ export async function ownSk(s: SkPickScope, ref: LaborRef): Promise { - const owner = s.layer === "system" ? null : s.owner; + const owner = await ownerOf(s); if (ref.층 === s.layer && (ref.주인 ?? null) === owner) return ref; - const got = await newLibrary( - s.layer, - "sangeun", - { 본뜬: { ...ref, 층: ref.층 as Layer } }, - owner, - ); + const 본뜬 = { ...ref, 층: ref.층 as Layer }; + const got = await newLibrary(s.layer, "sangeun", { 본뜬 }, s.owner); return { 층: s.layer, 주인: owner, 키: got.이름 }; } diff --git a/docs/raw/요구_1104/_목록_811-1_일위대가참조.xlsx b/docs/raw/요구_1104/_목록_811-1_일위대가참조.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..822572ec782187b8403fde942432a8680883cb7b GIT binary patch literal 7336 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 literal 0 HcmV?d00001 diff --git a/resources/tester/test_811_1_labor_ref.py b/resources/tester/test_811_1_labor_ref.py index fd8505b7c..a47daaf93 100644 --- a/resources/tester/test_811_1_labor_ref.py +++ b/resources/tester/test_811_1_labor_ref.py @@ -190,7 +190,12 @@ def test_화면_줄_넣기_SK_사본_참조_부품(): assert "return void skFromLogic(own, x.키).then(" in table # M01 기초 = SK 사본 assert "own && isSkRef(ref)" in table and "void ownSk(own, ref)" in table # 다른 층 = 사본 assert "if (isSkRef(row.참조) && !opt.calc)" in table # 줄 누름 = SK 수식 모달 - assert "/sangeun/from-logic" in pick and "본뜬:" in pick and "withSameAsk(" in pick + assert "/sangeun/from-logic" in pick and "{ 본뜬 }" in pick and "withSameAsk(" in pick + assert ( + "fetchSessionUser()" in pick + ) # 주인 null = 자기 층 → 서버 참조 꼴(같은 층 SK 를 사본으로 안 만듦) + combo = _src("M02_MasterTemplete_Labor_Combo.ts") + assert "...refCards(doc, calc)" in combo and "key: r.참조.키," in combo # 다이어그램 참조 카드 for text in (table, pick): assert len(text.splitlines()) <= 700 assert not re.search(r"""\bel\(\s*["'](button|input|select)["']""", text)