diff --git a/A00_Common/formula_doc_copy.ts b/A00_Common/formula_doc_copy.ts index 1b74e2e7c..1e0f92075 100644 --- a/A00_Common/formula_doc_copy.ts +++ b/A00_Common/formula_doc_copy.ts @@ -8,6 +8,7 @@ * (821-3) 값 줄만 바뀜(M01 단가 · 내 기계 고침) = 「새 값 있음」(같은 자리 · 같은 꼴) · 지운 키(M01 · 내 기계) = 「지워짐 n」 · * [단가 갱신] 확인 창 = 바뀐 자원 줄(이름 · 바뀐 칸 전 → 후 · 지워짐 = 마지막 값 유지) * (822-1) 값 사본에 없는 셈 줄(옛 저장본 빈틈) = 「사본 없는 줄 n」(지금 값으로 셈 · 저장하면 굳음) + * (836-2) 표지 `자동`(프로젝트 층 밖 = M01 자동 따라옴) = 단추 · 「새 값」 · 「사본 없는 줄」 안 그림(지워짐만) * ========================================================================== */ import { createBadge } from "@ui/ui_template_badge"; @@ -18,6 +19,7 @@ import type { FormulaDoc } from "../common_util/common_util_formula_doc"; import type { Basis } from "../common_util/common_util_formula_doc_import"; import { swappedCount } from "../common_util/common_util_line_stack_pick"; import { ft } from "./formula_doc_text"; +import type { RenewNote } from "./formula_doc_renew"; export type CopyJob = "단가갱신" | "재생성"; @@ -32,6 +34,10 @@ export interface CopyMark { 따로?: boolean; /** 가져온 식(엑셀 불러오기 · 807-1) 단가기준 — 엑셀 = 엑셀 값 기억 · 우리 = 연결 값 */ 단가기준?: Basis; + /** M01 자동 따라옴(836-2 · 프로젝트 층 밖) — [단가 갱신] · [재생성] · 「새 값」 · 「사본 없는 줄」 없음 */ + 자동?: boolean; + /** 열 때 알림(836-2) — M01 에서 바뀐 항목의 지운 고침 */ + 갱신?: RenewNote[]; } export interface CopyOptions { @@ -58,7 +64,7 @@ export function isStale(m: CopyMark | undefined): boolean { export function copyBadges(m: CopyMark | undefined, edit: boolean): HTMLElement[] { const out: HTMLElement[] = []; const doc = !!m?.M01새판 || !!m?.문서새판?.length; - if (doc || m?.단가새값) { + if ((doc || m?.단가새값) && !m?.자동) { const b = createBadge(ft(doc ? "New_M01" : "New_Value"), "danger"); b.dataset.copyStale = doc ? "doc" : "value"; out.push(b); @@ -69,7 +75,7 @@ export function copyBadges(m: CopyMark | undefined, edit: boolean): HTMLElement[ b.dataset.copyGone = m.지운키.join(","); out.push(b); } - if (m?.사본없음) { + if (m?.사본없음 && !m.자동) { const b = createBadge(`${ft("Gap")} ${m.사본없음}`, "warning"); b.title = ft("Gap_Note"); b.dataset.copyGap = String(m.사본없음); diff --git a/A00_Common/formula_doc_renew.ts b/A00_Common/formula_doc_renew.ts new file mode 100644 index 000000000..72b409f88 --- /dev/null +++ b/A00_Common/formula_doc_renew.ts @@ -0,0 +1,45 @@ +/* ============================================================================= + * formula_doc_renew.ts + * M01 자동 따라옴 알림(PLAN 836-2 · 요구 854) — M02 단가산출(프로젝트 층 밖)을 열 때 + * M01 에서 바뀐 항목에 얹힌 내 고침을 지우고 다시 복사한 것을 한 번 보임(서버 `M02_Sk_AutoRenew.notes`) + * 표 = 항목 · M01 전 → 후 · 지워진 내 고침 · [확인] = 저장(다시 안 뜸) · [나중에] = 다음에 열면 또 뜸 + * 고침 없는 항목은 조용히 따라옴(알림 없음) + * ========================================================================== */ + +import { askConfirm, el } from "@ui/ui_template_elements"; +import { createTable } from "@ui/ui_template_table"; +import { ft } from "./formula_doc_text"; + +/** 서버 알림 한 줄 — 갈래 = 문서(바탕 수식) · 부름(굳힌 부른 문서) · 기계(기계 덮음) */ +export interface RenewNote { + 키: string; + 이름?: string; + 갈래?: string; + M01?: string[]; + 내고침?: string[]; +} + +/** 칸 안 여러 줄 — 줄마다 한 덩이 */ +const lines = (xs: string[] | undefined): HTMLElement => + el("div", { children: (xs ?? []).map((t) => el("div", { text: t })) }); + +/** 알림 창 — 확인이면 true(부르는 쪽이 저장) */ +export function askRenew(notes: RenewNote[]): Promise { + const table = createTable( + [ + { label: ft("Renew_Item"), width: "28%" }, + { label: ft("Renew_M01") }, + { label: ft("Renew_Mine") }, + ], + notes.map((n) => [lines([n.이름 || n.키, n.키]), lines(n.M01), lines(n.내고침)]), + { density: "data" }, + ); + table.dataset.renewNotes = String(notes.length); + return askConfirm({ + title: ft("Renew_Title"), + message: ft("Renew_Ask"), + okLabel: ft("Renew_Ok"), + cancelLabel: ft("Renew_Later"), + detail: table, + }); +} diff --git a/A00_Common/formula_doc_text.ts b/A00_Common/formula_doc_text.ts index dd7607219..a20e06c60 100644 --- a/A00_Common/formula_doc_text.ts +++ b/A00_Common/formula_doc_text.ts @@ -78,6 +78,16 @@ const TEXT = { Changed: ["바뀐 자원", "Changed resources"], Before_After: ["계", "Sum"], Job_Done: ["적용함", "Applied"], + Renew_Title: ["M01 이 바뀌어 다시 복사함", "Re-copied from updated M01"], + Renew_Ask: [ + "M01 에서 바뀐 항목에 있던 내 고침을 지우고 M01 에서 다시 복사함 — [확인] 하면 저장(다시 안 뜸)", + "Your edits on items changed in M01 were removed and re-copied — OK saves (no repeat)", + ], + Renew_Item: ["항목", "Item"], + Renew_M01: ["M01 전 → 후", "M01 before → after"], + Renew_Mine: ["지워진 내 고침", "Removed edits"], + Renew_Ok: ["확인", "OK"], + Renew_Later: ["나중에", "Later"], Basis_Excel: ["엑셀 단가 그대로", "Excel prices"], Basis_Ours: ["우리 단가로", "Our prices"], Dirty_First: [ diff --git a/A00_Common/line_stack_screen.ts b/A00_Common/line_stack_screen.ts index 3e917b35d..2ce9e5484 100644 --- a/A00_Common/line_stack_screen.ts +++ b/A00_Common/line_stack_screen.ts @@ -49,6 +49,7 @@ import { swapBadge, } from "./formula_doc_copy"; import { docFoot } from "./formula_doc_foot"; +import { askRenew } from "./formula_doc_renew"; import { buildOptions } from "./formula_doc_options"; import { ft } from "./formula_doc_text"; import { addTaken, type Taken } from "../common_util/common_util_line_stack_copy"; @@ -306,7 +307,7 @@ export function createStackScreen(opt: StackScreenOptions) { const busy = dirty(); const tools = [ ...(mark?.단가기준 && opt.page ? [basisPick(mark.단가기준)] : []), // 모달 = 머리 판 단추(793-3b) - ...(cp && edit() && key ? copyTools(true, (job) => void runJob(job)) : []), + ...(cp && edit() && key && !mark?.자동 ? copyTools(true, (job) => void runJob(job)) : []), // 836-2 자동 = 없음 ...(edit() && key && !opt.page ? [ createButton({ @@ -517,6 +518,9 @@ export function createStackScreen(opt: StackScreenOptions) { recalc(); draw(); opt.page?.(); // 캐시에서 이어 받은 고침 — 페이지 [저장] · [되돌림] 켬 + const notes = (mark as CopyMark | undefined)?.갱신; // 836-2 M01 자동 따라옴 알림(take 가 채움) + if (cp && edit() && !dirty() && notes?.length) + void askRenew(notes).then((ok) => ok && save({ 할일: "갱신확인", 판: version }, "")); } catch (e) { if (mine === seq) head.replaceChildren( diff --git a/M02_MasterTemplete/M02_MyMachine_Text.ts b/M02_MasterTemplete/M02_MyMachine_Text.ts index 28e68ff94..b4f313ab9 100644 --- a/M02_MasterTemplete/M02_MyMachine_Text.ts +++ b/M02_MasterTemplete/M02_MyMachine_Text.ts @@ -33,7 +33,7 @@ const TEXT = { Copied: "「{value}」 값을 복사 — 고친 뒤 [저장]", Own_Value: "개인 값", Own_Note: - "개인 값 = 이 기계 고유 값(가격 · 손료계수 · 연료 ℓ · 잡품 % · 조종원 수) · 단가(노임 · 유가)는 M01 값 · 단가산출엔 만들 때 복사 → 고친 뒤 「새 값」 → [단가 갱신]", + "개인 값 = 이 기계 고유 값(가격 · 손료계수 · 연료 ℓ · 잡품 % · 조종원 수) · 단가(노임 · 유가)는 M01 값 · 단가산출(프로젝트 밖)은 고치면 저절로 따라옴", Server_Hourly: "서버 셈 시간당", Hourly_None: "시간당 못 셈", Uses: "쓰는 단가산출", @@ -59,7 +59,7 @@ const TEXT = { Win_M01: "M01 기계", Win_Copy: "이 단가산출에 복사된 값", Win_Live: "원본 지금 값", - Win_Newer: "원본에 새 값 있음 — [단가 갱신] 으로 반영", + Win_Newer: "원본에 새 값 있음 — 프로젝트 사본은 [단가 갱신] 으로 반영", Win_Swap: "이 기계로 바꾸기", Win_Edit: "고쳐 쓰기", Win_Blank: "빈 양식에서 새 기계", @@ -67,7 +67,7 @@ const TEXT = { Win_Cancel: "취소", Win_Edit_Note: "[확인] = 이 단가산출에 초안으로 끼움 · 단가산출 [저장] 때 내 기계로 저장 · 저장 안 하고 나가면 사라짐", - Win_Uses: "이 기계를 쓰는 다른 단가산출 {value} 건 — 저장하면 그쪽에 「새 값」 배지", + Win_Uses: "이 기계를 쓰는 다른 단가산출 {value} 건 — 저장하면 그쪽도 새 값으로 셈", Win_Hint: "왼쪽에서 기계를 고르면 값이 보임", Win_View: "조회", Why_M01: "기계 값은 M01 기계 상세에서", diff --git a/M02_MasterTemplete/M02_Sk_AutoRenew.py b/M02_MasterTemplete/M02_Sk_AutoRenew.py index 0f9d8285f..db33ebaac 100644 --- a/M02_MasterTemplete/M02_Sk_AutoRenew.py +++ b/M02_MasterTemplete/M02_Sk_AutoRenew.py @@ -125,7 +125,7 @@ def diff(a: Any, b: Any) -> list[str]: if dc.fp(x) == dc.fp(y): continue name = str((y or x or {}).get("이름") or f"금액 {n + 1}") - show = lambda z: "—" if not z else f"{z.get('수량') or ''} × {z.get('단가') or ''}" # noqa: E731 + show = lambda z: " × ".join(str(z[k]) for k in ("수량", "단가") if z and z.get(k)) or "—" # noqa: E731 out.append(f"{name} {show(x)} → {show(y)}") for slot in ("식", "끝수", "결과단위"): if dc.fp(a.get(slot)) != dc.fp(b.get(slot)):