From 2149351b3450cb9bd36c843d3c1fba06bc39437b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 13 Nov 2026 10:36:33 +0900 Subject: [PATCH] =?UTF-8?q?fix(827-1):=20=EA=B8=B0=EA=B3=84=20=EC=B0=BD=20?= =?UTF-8?q?[=ED=99=95=EC=9D=B8]=20=EC=A7=81=ED=9B=84=20=EA=B8=88=EC=95=A1?= =?UTF-8?q?=20=EC=A6=89=EC=8B=9C=20=E2=80=94=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B8=EC=9D=B4=20=EA=B7=B8=20=EC=8B=9D=20=EC=85=88=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=EC=9D=84=20=EC=A4=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서버 확인 길(my-machines/draft) = 화면이 정한 표지로 그 식 한 줄 셈 기록(요소 · 표 · 식 문서) 돌려줌 - 화면은 기록을 계산기 캐시에 합침 → 서버 왕복 전 0 원 깜빡임 없음(미리 셈 값과 같음) - 기계 창 목록 열 이름 「구분」 · 폭 조정 - 시험 test_827_1 ⑦ 셈 기록 확인 더함 Co-Authored-By: Claude Opus 5.5 --- A00_Common/line_stack_edit.ts | 9 +++--- A00_Common/line_stack_machine.ts | 18 ++++++----- A00_Common/line_stack_screen.ts | 7 ++--- M02_MasterTemplete/M02_MyMachine_Api.ts | 13 ++++++-- M02_MasterTemplete/M02_MyMachine_Draft.py | 30 +++++++++++++++++-- M02_MasterTemplete/M02_MyMachine_Router.py | 1 + M02_MasterTemplete/M02_MyMachine_Text.ts | 1 + resources/tester/test_827_1_machine_window.py | 5 +++- 8 files changed, 60 insertions(+), 24 deletions(-) diff --git a/A00_Common/line_stack_edit.ts b/A00_Common/line_stack_edit.ts index a7b797214..77965cad0 100644 --- a/A00_Common/line_stack_edit.ts +++ b/A00_Common/line_stack_edit.ts @@ -20,6 +20,7 @@ import { varId, type FormulaDoc, type MoneyLine } from "../common_util/common_ut import { isNum, type DocAnswer } from "../common_util/common_util_formula_doc_calc"; import { lineMap, offLine, type Values } from "../common_util/common_util_formula_doc_view"; import type { DocTable } from "../common_util/common_util_formula_doc_table"; +import type { DocRecord } from "../common_util/common_util_formula_doc_cache"; import { dropSyms, docOf, @@ -78,8 +79,8 @@ export interface EditHooks { before?: ReadonlyMap; /** 827-1 기계 줄 = 기계 창(조회 · 바꾸기 · 고쳐 쓰기) — 층 · 권한 · 사본 줄 */ machine?: MachineCtx; - /** 827-1 초안 기계 셈 꼴 값 → 계산기 캐시(브라우저 즉시 셈 · 서버 미리 셈과 같은 값) */ - prime?: (no: string, values: Record) => void; + /** 827-1 초안 기계 셈 기록 → 계산기 캐시(브라우저 즉시 셈 · 서버 미리 셈과 같은 값) */ + prime?: (record: DocRecord) => void; } const field = ( @@ -188,8 +189,8 @@ function moneyBody(h: EditHooks, i: number): HTMLElement[] { now: machineNow(d, link as { 갈래?: string; 입력?: Record }), query, pick: swap, - draft: (item, p, values) => { - h.prime?.(`개인 ${item.표지}`, values); + draft: (item, p, record) => { + h.prime?.(record); h.change((doc) => putDraft(doc, i, own, item, p)); }, }) diff --git a/A00_Common/line_stack_machine.ts b/A00_Common/line_stack_machine.ts index 4605e1ef7..031611d93 100644 --- a/A00_Common/line_stack_machine.ts +++ b/A00_Common/line_stack_machine.ts @@ -17,6 +17,7 @@ import { createMachineForm, HOURLY_KEY, type MachineValues } from "@ui/ui_templa import { openModal } from "@ui/ui_template_modal"; import { createText } from "@ui/ui_template_text"; import { isDraftNo, newTag, type DraftItem } from "../common_util/common_util_line_stack_draft"; +import type { DocRecord } from "../common_util/common_util_formula_doc_cache"; import type { Picked } from "../common_util/common_util_line_stack_pick"; import { call, type Layer } from "../M02_MasterTemplete/M02_MasterTemplete_Api_Fetch"; import { @@ -70,7 +71,8 @@ export interface MachineWindowOptions { query: string; /** 없으면 보기만(옵션 가로 조회) */ pick?: (p: Picked) => void; - draft?: (d: DraftItem, p: Picked, values: Record) => void; + /** 고쳐 쓰기 [확인] — 초안 · 줄 연결 · 그 식 셈 기록(계산기 캐시 · 즉시 셈) */ + draft?: (d: DraftItem, p: Picked, record: DocRecord) => void; } type Kind = "now" | "draft" | "mine" | "m01"; @@ -154,10 +156,10 @@ export function openMachineWindow(o: MachineWindowOptions): void { const table = createDataTable({ caption: mt("Win_Title"), columns: [ - { key: "품명", label: mt("Name"), width: 170 }, - { key: "규격", label: mt("Spec"), width: 100 }, - { key: "금액", label: mt("Hourly"), kind: "number", decimals: 0, width: 90 }, - { key: "구분", label: mt("How"), width: 120 }, + { key: "품명", label: mt("Name"), width: 150 }, + { key: "규격", label: mt("Spec"), width: 80 }, + { key: "금액", label: mt("Hourly"), kind: "number", decimals: 0, width: 84 }, + { key: "구분", label: mt("Win_Kind"), width: 110 }, ], rows: [], rowClass: (i) => (same(cands[i], sel) ? "is-title" : null), @@ -452,10 +454,10 @@ export function openMachineWindow(o: MachineWindowOptions): void { if (!body) return; btn.disabled = true; try { - const got = await checkDraft(owner, body, s.target); + const tag = s.target.표지 ?? newTag(); + const got = await checkDraft(owner, body, { ...s.target, 표지: tag }); if (!got.시간당.ok || !got.시간당.로직) throw new Error(hourlyText(got.시간당) || mt("Pick_Direct")); - const tag = s.target.표지 ?? newTag(); const item: DraftItem = { 표지: tag, ...(s.target.키 ? { 키: s.target.키 } : {}), @@ -471,7 +473,7 @@ export function openMachineWindow(o: MachineWindowOptions): void { }; formDirty = () => false; close(); - o.draft!(item, p, got.셈값); + o.draft!(item, p, got.연결); } catch (error) { notice.replaceChildren( createText(error instanceof Error ? error.message : mt("Failed"), "error"), diff --git a/A00_Common/line_stack_screen.ts b/A00_Common/line_stack_screen.ts index 00403214e..3e917b35d 100644 --- a/A00_Common/line_stack_screen.ts +++ b/A00_Common/line_stack_screen.ts @@ -282,11 +282,8 @@ export function createStackScreen(opt: StackScreenOptions) { ? { copyRow: (no: string) => call(`/machine?no=${encodeURIComponent(no)}`) } : {}), }); - /** 초안 기계 셈 꼴 값 → 계산기 캐시(요소 `EQ:{기계}` 칸마다 · 서버 미리 셈이 같은 값으로 덮음) */ - const prime = (no: string, values: Record): void => - cache.merge({ - 요소: Object.entries(values).map(([칸, 값]) => ({ p: { 키: `EQ:${no}`, 칸 }, 값 })), - }); + /** 초안 기계 셈 기록(서버 확인 길 · 요소 · 표 · 식 문서) → 계산기 캐시 — [확인] 직후 브라우저가 바로 셈 */ + const prime = (record: DocRecord): void => cache.merge(record); const draw = (): void => { if (!doc || !answer) { [head, left, right, foot].forEach((x) => x.replaceChildren()); diff --git a/M02_MasterTemplete/M02_MyMachine_Api.ts b/M02_MasterTemplete/M02_MyMachine_Api.ts index 4f1e5a3db..7abc91b76 100644 --- a/M02_MasterTemplete/M02_MyMachine_Api.ts +++ b/M02_MasterTemplete/M02_MyMachine_Api.ts @@ -6,6 +6,7 @@ * ========================================================================== */ import type { MachinePick, MachineValues } from "@ui/ui_template_machine_form"; +import type { DocRecord } from "../common_util/common_util_formula_doc_cache"; import { call } from "./M02_MasterTemplete_Api_Fetch"; import type { M01Element } from "./M02_MasterTemplete_Find_Pick"; @@ -155,20 +156,26 @@ export const mineElement = ( 원문번호: row.원문번호, }); -/** 기계 창 [확인](827-1) — 초안 몸 검사 · 셈 꼴 값 · 시간당(식 키) · 쓰기 없음 */ +/** 기계 창 [확인](827-1) — 초안 몸 검사 · 셈 꼴 값 · 시간당(식 키) · 그 식 셈 기록(계산기 캐시) · 쓰기 없음 */ export interface DraftCheck { 줄: MyMachine; 셈값: Record; 시간당: MyHourly; + 연결: DocRecord; } export const checkDraft = ( owner: string | null, body: MyMachineBody, - target: { 키?: string; 본뜬?: string }, + target: { 표지: string; 키?: string; 본뜬?: string }, ): Promise => call(at(owner, "/draft"), { method: "POST", - body: JSON.stringify({ 줄: body, 키: target.키 ?? null, 본뜬: target.본뜬 ?? null }), + body: JSON.stringify({ + 줄: body, + 표지: target.표지, + 키: target.키 ?? null, + 본뜬: target.본뜬 ?? null, + }), }); /** M01 기계 줄 값 + 시간당(기계 창 목록 · 양식 · 60 까지) */ diff --git a/M02_MasterTemplete/M02_MyMachine_Draft.py b/M02_MasterTemplete/M02_MyMachine_Draft.py index 1921d467f..cfb51347b 100644 --- a/M02_MasterTemplete/M02_MyMachine_Draft.py +++ b/M02_MasterTemplete/M02_MyMachine_Draft.py @@ -64,16 +64,40 @@ def _row(item: dict, have: dict[str, dict]) -> dict: def check(item: Any) -> dict[str, Any]: - """기계 창 [확인] — {줄, 셈값(셈 꼴 값 칸), 시간당(식 키)} · 쓰기 없음 · 틀리면 400.""" + """기계 창 [확인] — {줄, 셈값(셈 꼴 값 칸), 시간당(식 키), 연결(그 식 한 줄 셈 기록 = 화면 계산기 캐시)} · + 쓰기 없음 · 틀리면 400 · `표지` = 화면이 정한 초안 표지(기록 요소 열쇠가 줄과 같게).""" item = item if isinstance(item, dict) else {} row = _row({**item, "표지": item.get("표지") or "EQP초안-0"}, _have()) eng = mym.engine(row) token = mym.DRAFT.set((eng,)) try: - hour = mym.hourly(eng, mym.whole()) + whole = mym.whole() + hour = mym.hourly(eng, whole) + rec = _record(whole, hour.get("로직"), row["원문번호"]) if hour.get("ok") else {} finally: mym.DRAFT.reset(token) - return {"줄": row, "셈값": prices.to_json(eng["값"]), "시간당": hour} + out = {"줄": row, "셈값": prices.to_json(eng["값"]), "시간당": hour, "연결": rec} + return json.loads(json.dumps(out, ensure_ascii=False, default=str)) + + +def _record(whole: Any, logic: Any, no: str) -> dict: + """그 식을 기계 하나로 부르는 한 줄 문서의 셈 기록(요소 · 표 · 문서) — 화면이 [확인] 직후 바로 셈.""" + import master_doc_record as mr + + from M02_MasterTemplete import M02_Doc_Copy as dc + + var = {"기호": "P", "아래": "기계", "위": "", "이름": "기계", "단위": "", + "연결": {"갈래": "단가산출", "키": logic, "입력": {"기계": f"'{no}'"}}} # fmt: skip + line = {"이름": "기계", "단위": "hr", "비목": "묶음", "수량": "1", "단가": "P_기계"} + one = {"꼴": "수식문서", "판": 1, "키": "_기계창", "이름": "기계", "결과단위": "원", + "변수": [var], "식": [], "금액": [line]} # fmt: skip + rec = mr.Rec(dc.ml.Links(whole, dc.closure(whole, [str(logic)]))) + try: + rec.prefetch(one) + dc.mc.calc(one, rec) + except dc._ERR: + pass + return dc.ml._norm(rec.out()) @contextmanager diff --git a/M02_MasterTemplete/M02_MyMachine_Router.py b/M02_MasterTemplete/M02_MyMachine_Router.py index 01816163d..45efc3d96 100644 --- a/M02_MasterTemplete/M02_MyMachine_Router.py +++ b/M02_MasterTemplete/M02_MyMachine_Router.py @@ -28,6 +28,7 @@ class MachineBody(BaseModel): 줄: dict[str, Any] | None = None 본뜬: str | None = None 키: str | None = None # 초안 확인(827-1) — 고칠 내 기계 + 표지: str | None = None # 초안 확인(827-1) — 화면이 정한 표지(셈 기록 열쇠) def _folder(session: Session, layer: str, owner: str | None, write: bool = False): diff --git a/M02_MasterTemplete/M02_MyMachine_Text.ts b/M02_MasterTemplete/M02_MyMachine_Text.ts index 3d51c9d8a..28e68ff94 100644 --- a/M02_MasterTemplete/M02_MyMachine_Text.ts +++ b/M02_MasterTemplete/M02_MyMachine_Text.ts @@ -53,6 +53,7 @@ const TEXT = { View_Note: "보기 · 쓰는 곳 · 지우기만 — 만들기 · 고치기는 단가산출의 기계 줄에서", // 단가산출 기계 창(827-1) Win_Title: "기계", + Win_Kind: "구분", Win_Now: "지금 이 줄", Win_Draft: "초안 — 단가산출 [저장] 때 내 기계로", Win_M01: "M01 기계", diff --git a/resources/tester/test_827_1_machine_window.py b/resources/tester/test_827_1_machine_window.py index cfd8d3747..0c08055ed 100644 --- a/resources/tester/test_827_1_machine_window.py +++ b/resources/tester/test_827_1_machine_window.py @@ -5,7 +5,7 @@ ② 같은 초안 두 줄 = 내 기계 하나 ③ 저장 안 함(미리 셈만) = 내 기계 0 ④ 실패(판 다름 · 저장 중 오류) = 내 기계 · SK 그대로(만든 것 지움 · 고친 것 되돌림) ⑤ 내 기계 고침 초안 → 그 SK 는 새 값 · 그 기계 쓰는 다른 SK 「단가새값」 → [단가 갱신] -⑥ 회사 층 · 남의 층 거절 ⑦ 확인 길 = 쓰기 없음 · 틀린 몸 400 ⑧ M01 기계 목록 시간당 = 정본 시간당. +⑥ 회사 층 · 남의 층 거절 ⑦ 확인 길 = 쓰기 없음 · 셈 기록(화면 즉시 셈) · 틀린 몸 400 ⑧ M01 기계 목록 시간당 = 정본 시간당. """ from __future__ import annotations @@ -206,6 +206,9 @@ def test_check_no_write(world): "시간당": {"노무비": 10, "재료비": 20, "경비": 30}}}) # fmt: skip assert got["시간당"]["로직"] == "GC001199" and got["시간당"]["계"] == 60 assert got["셈값"] == {"노무비": 10, "재료비": 20, "경비": 30} + rec = _at(me, draft.check, {"줄": _body(), "표지": "EQP초안-55"})["연결"] # 화면 계산기 캐시 + assert {"키": "EQ:개인 EQP초안-55", "칸": "가격"} in [e["p"] for e in rec["요소"]] + assert rec["문서"] and "CC000336" in rec["표"] for raw, code in (({"줄": {"이름": ""}}, 400), ({"줄": _body(), "키": "EQP42-0099"}, 404)): with pytest.raises(store.StoreError) as e: _at(me, draft.check, raw)