Merge branch 'sub_laptop_1' of C:/Aislo_wt/sub_laptop_1 into main_laptop_1
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
* M02 사본(`copy`) — 저장 몸 {문서, 끊음(받은 그대로), 판} · 판 바뀜(409)은 문서가 그대로면 새 판으로 한 번 더
|
||||
* 기계 줄 · 옵션 가로 기계 = 기계 창(827-1 · `line_stack_machine`) — 초안은 문서 칸(캐시) · [저장] 때 서버가 내 기계로
|
||||
* [단가 갱신] · [재생성] = 미리 셈 → 알림 → 저장(`formula_doc_copy.ts` · 고친 것 먼저 저장)
|
||||
* 저장 전 문서(`memory` · 836-5 · 불러오기 미리 보기) = 빈 키 + 서버에 문서째(`본`) 읽음 · 왼쪽 줄 쌓기는 보기만(inert) · 디스크 쓰기 0
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
@@ -74,6 +75,8 @@ export interface StackScreenOptions {
|
||||
page?: () => void;
|
||||
/** 페이지 머리 칸(808-1 · 요구 701-3) — 있으면 [단가 갱신] · [재생성] 등 화면 단추를 여기에(화면 위 단추 줄 대신) */
|
||||
tools?: HTMLElement;
|
||||
/** 저장 전 SK 문서(836-5 · 불러오기 미리 보기) — 빈 키일 때 이 문서를 서버가 읽어 줌(파일 안 읽음 · 안 씀) */
|
||||
memory?: () => Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
type Docs = Record<string, FormulaDoc>;
|
||||
@@ -126,14 +129,21 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
let paired = ""; // 짝 강조 자리(679 — 항목 `k` · 줄 `k.j`)
|
||||
const blocks = new Set<string>(); // 풀린 공식 묶음
|
||||
|
||||
const mem = (): Record<string, unknown> | null => (cp && !key ? (opt.memory?.() ?? null) : null);
|
||||
const call = async <T>(path: string, body?: unknown, signal?: AbortSignal): Promise<T> => {
|
||||
const r = await fetch(`${API_BASE_URL}${cp ? cp.route(key, path) : opt.base + path}`, {
|
||||
method: body === undefined ? "GET" : "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
||||
signal,
|
||||
});
|
||||
const saved = mem(); // 저장 전 문서 = 길 키 「-」 · 몸 `본`
|
||||
const r = await fetch(
|
||||
`${API_BASE_URL}${cp ? cp.route(saved ? "-" : key, path) : opt.base + path}`,
|
||||
{
|
||||
method: body === undefined ? "GET" : "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
...(body === undefined
|
||||
? {}
|
||||
: { body: JSON.stringify(saved ? { ...(body as object), 본: saved } : body) }),
|
||||
signal,
|
||||
},
|
||||
);
|
||||
const data = (await r.json().catch(() => ({}))) as T & { detail?: unknown };
|
||||
if (!r.ok) {
|
||||
const why = data.detail;
|
||||
@@ -346,9 +356,9 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
);
|
||||
opt.tools?.replaceChildren(...tools);
|
||||
const items = stackOf(doc);
|
||||
root.dataset.edit = edit() ? "1" : "";
|
||||
root.dataset.edit = edit() || mem() ? "1" : "";
|
||||
left.replaceChildren(
|
||||
...(edit()
|
||||
...(edit() || mem()
|
||||
? [
|
||||
buildEdit({
|
||||
doc,
|
||||
@@ -368,6 +378,7 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
]
|
||||
: []),
|
||||
);
|
||||
left.inert = !!mem(); // 저장 전 문서 = 보기만(고침은 저장 뒤)
|
||||
right.replaceChildren(
|
||||
items.length || doc.결과
|
||||
? buildOut(doc, answer, {
|
||||
@@ -494,6 +505,26 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
[cut, mark, version] = [{}, undefined, ""];
|
||||
draw();
|
||||
if (!next) {
|
||||
if (mem()) {
|
||||
// 저장 전 문서(836-5) — 서버가 이 문서째 읽어 줌
|
||||
const mine = ++seq;
|
||||
try {
|
||||
const got = await call<Loaded>("/read", {});
|
||||
if (mine !== seq || key !== next) return;
|
||||
take(got);
|
||||
recalc();
|
||||
return draw();
|
||||
} catch (e) {
|
||||
if (mine === seq)
|
||||
head.replaceChildren(
|
||||
el("p", {
|
||||
className: "ui-text ui-text--muted",
|
||||
text: `${ft("Load_Failed")} — ${e instanceof Error ? e.message : ""}`,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cp)
|
||||
return void right.replaceChildren(
|
||||
el("p", { className: "ui-text ui-text--muted", text: lt("Unsaved_New") }),
|
||||
|
||||
@@ -47,7 +47,6 @@ import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import { askBasicInfo } from "./M01_MasterData_UI_LogicLab_Basic";
|
||||
import { saveRevised } from "./M01_MasterData_UI_LogicLab_Revise";
|
||||
import { buildLogicSide } from "./M01_MasterData_UI_LogicLab_Side";
|
||||
import { createViewPicker } from "./M01_MasterData_UI_LogicLab_View";
|
||||
import { createStackScreen } from "../A00_Common/line_stack_screen";
|
||||
import { buildHeadBar } from "./M01_MasterData_UI_Head";
|
||||
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
||||
@@ -123,10 +122,7 @@ export async function mountM01LogicLab(
|
||||
const editor = el("div", { className: "m01-logic__editor" });
|
||||
// 로직 상세 — 왼쪽 지금 내용 · 오른쪽 접는 패널 「원문」
|
||||
const panes = buildLogicSide(editor);
|
||||
// 보기 고르기 — 머리줄 [신규 로직] 왼쪽
|
||||
const views = createViewPicker(editor, (v) => {
|
||||
if (v === "stack") stack.draw(opened?.origKey ?? "", true);
|
||||
});
|
||||
editor.dataset.view = "stack"; // 보기 = 수식 하나(836-3) — 「단가산출 상세」 덩이는 CSS 로 숨김
|
||||
const errors = el("div", {
|
||||
className: "ui-banner ui-banner--danger m01-logic__reasons",
|
||||
attrs: { hidden: "" },
|
||||
@@ -233,7 +229,7 @@ export async function mountM01LogicLab(
|
||||
list.root.hidden = !listing || !loaded;
|
||||
waiting.hidden = loaded;
|
||||
panes.root.hidden = !opened;
|
||||
back.hidden = views.root.hidden = listing;
|
||||
back.hidden = listing;
|
||||
// 로직을 골라야 뜻 있는 단추 = 목록에선 숨김(요구 693 · [신규 로직]만 보임)
|
||||
copyButton.hidden = deleteButton.hidden = discardButton.hidden = saveButton.hidden = listing;
|
||||
if (!opened) return;
|
||||
@@ -542,7 +538,6 @@ export async function mountM01LogicLab(
|
||||
title: tx("Title"),
|
||||
status: [pending],
|
||||
buttons: [
|
||||
views.root,
|
||||
createButton({
|
||||
label: tl("New"),
|
||||
variant: "filled",
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData_UI_LogicLab_View.ts
|
||||
* 「단가산출 로직」 상세 보기 고르기 — 머리줄 [신규 로직] 왼쪽 「분할 고르기 | 세로 나눔선」
|
||||
* 보기 둘 = 수식(기본 · 줄 쌓기 화면 `A00_Common/line_stack_screen.ts` · 804-6 옛 수식 문서 탭 걷음) · 단가산출 상세 — 고른 덩이만 보임
|
||||
* 「단가산출 로직」 상세 보기 이름표 — 보기 = 수식(줄 쌓기 `A00_Common/line_stack_screen.ts`) 하나 · 고르기 단추 없음(836-3)
|
||||
* 그릇 `data-view="stack"` 고정 — `data-block="ho"` 덩이(단가산출 상세 표)는 CSS 로 숨김
|
||||
* 덩이 = `data-block` 이름표 · 숨김 = 그릇 `data-view` + CSS(`LogicLab_Style.css`) — 다시 그려도 고름 유지
|
||||
* 셈 구동은 화면 칸 없이 `_Logic_Drive.ts` · 기본정보는 이름표 없음(늘 맨 위)
|
||||
* 기억 = 이 화면 안에서만(로직을 바꿔도 유지 · 새로 들어오면 수식)
|
||||
* ========================================================================== */
|
||||
|
||||
import { createSegment } from "@ui/ui_template_segment";
|
||||
import { createToolbar } from "@ui/ui_template_toolbar";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
|
||||
export type LabView = "stack" | "ho";
|
||||
|
||||
/** 덩이에 보기 이름표를 붙여 돌려줌 */
|
||||
@@ -18,25 +13,3 @@ export const block = <T extends HTMLElement>(node: T, view: LabView): T => {
|
||||
node.dataset.block = view;
|
||||
return node;
|
||||
};
|
||||
|
||||
/** 보기 고르기 — `host`(상세 그릇)에 `data-view` 를 둠 · `root` 는 머리줄에 놓음 */
|
||||
export function createViewPicker(
|
||||
host: HTMLElement,
|
||||
onChange?: (v: LabView) => void,
|
||||
): { root: HTMLElement } {
|
||||
host.dataset.view = "stack";
|
||||
const segment = createSegment({
|
||||
options: [
|
||||
{ value: "stack", label: tl("View_Equation") },
|
||||
{ value: "ho", label: tl("Ho_Title") },
|
||||
],
|
||||
value: "stack",
|
||||
size: 32,
|
||||
ariaLabel: tl("View_Label"),
|
||||
onChange: (v) => {
|
||||
host.dataset.view = v;
|
||||
onChange?.(v as LabView);
|
||||
},
|
||||
});
|
||||
return { root: createToolbar({ items: [segment.root, "divider"], label: tl("View_Label") }) };
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
POST /api/m02/layers/{층}/sangeun/{키}/doc/preview 미리 셈(층 쓰기) {문서?, 끊음?, 할일?}
|
||||
→ {…, 전, 후, 바뀐자원?, 버림?}
|
||||
POST /api/m02/layers/{층}/sangeun/{키}/doc/save 저장(층 쓰기) {문서?, 끊음?, 할일?, 판} → 읽기 꼴
|
||||
POST /api/m02/layers/{층}/sangeun/-/doc/read 저장 전 문서 읽기(층 읽기 · 836-5) {본} → 읽기 꼴 · 미리 셈 몸에도 `본` 가능
|
||||
GET /api/m02/layers/{층}/sangeun/{키}/doc/machine?no= 기계 창(827-1 · 층 읽기) → {사본, 지금, 새값}
|
||||
옛 별칭 장부 쓰기(812-9) · 연결 찾기 길(810-4)은 걷음(815-2 · 요구 762-1 · 765).
|
||||
⚠ 등록은 `main.py`(로그인만 · 층 권한은 길 안에서).
|
||||
@@ -29,6 +30,7 @@ class DocBody(BaseModel):
|
||||
할일: str | None = None
|
||||
판: str | None = None
|
||||
단가기준: str | None = None # 가져온 식만(807-1 · 엑셀 | 우리)
|
||||
본: dict[str, Any] | None = None # 저장 전 SK 문서째(836-5 · 파일 안 읽음 · 안 씀)
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/sangeun/{key}/doc")
|
||||
@@ -49,6 +51,16 @@ def machine_doc(
|
||||
return _call(_root(session, layer, owner), path.machine, key, no)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/sangeun/-/doc/read")
|
||||
def read_memory_doc(
|
||||
layer: Layer,
|
||||
body: DocBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
return _call(_root(session, layer, owner), path.read_memory, body.본)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/sangeun/{key}/doc/preview")
|
||||
def preview_doc(
|
||||
layer: Layer,
|
||||
@@ -57,7 +69,7 @@ def preview_doc(
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
folder = _root(session, layer, owner, write=body.본 is None) # 본 = 안 씀 → 읽기 권한이면 됨
|
||||
return _call(folder, path.preview, key, body.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ M01 수식 문서 길과 같은 꼴: 읽기 {문서, 셈} · 미리 셈 {문서}
|
||||
단가갱신 = 값 줄만 지금 M01(문서 그대로).
|
||||
가져온 식 SK(807-1) = 가져온 줄 문서 · 셈 = 단가기준(엑셀 값 · 연결 값) · 부름 · 끊음 없음
|
||||
(형제 SK 는 셈 때 읽음) · 단가갱신 = 문서 그대로 · 바탕 참조 SK = 값 사본 없음(400).
|
||||
저장 전 문서(836-5 · 불러오기 미리 보기 835-1) = 몸 `본`(그 SK 문서째) — 파일 안 읽음 · 안 씀 · 읽기 · 미리 셈만(`read_memory`).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -105,6 +106,20 @@ def read(key: str) -> dict[str, Any]:
|
||||
return out
|
||||
|
||||
|
||||
def read_memory(sk: Any) -> dict[str, Any]:
|
||||
"""저장 전 문서 읽기(836-5) — 몸 `본` 그대로 셈 · {read 꼴 · 판 = 빈 글} · 디스크 쓰기 0."""
|
||||
doc = _mem(sk)
|
||||
return {**answer(current(doc), doc), "판": ""}
|
||||
|
||||
|
||||
def _mem(raw: Any) -> dict:
|
||||
if not isinstance(raw, dict):
|
||||
raise _bad("본 · 묶음 아님")
|
||||
if len(json.dumps(raw, ensure_ascii=False, default=str).encode("utf-8")) > MAX:
|
||||
raise _bad("본 · 너무 큼")
|
||||
return raw
|
||||
|
||||
|
||||
# ── 몸 검사 ───────────────────────────────────────────────────────────
|
||||
def _doc(raw: Any, key: str, where: str) -> dict:
|
||||
if not isinstance(raw, dict):
|
||||
@@ -166,7 +181,11 @@ def preview(key: str, body: dict) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _preview(key: str, body: dict) -> dict[str, Any]:
|
||||
doc = _file(key)["문서"]
|
||||
if body.get("본") is not None: # 저장 전 문서(836-5) — 파일 아님
|
||||
doc = _mem(body["본"])
|
||||
key = str(doc.get("키") or "")
|
||||
else:
|
||||
doc = _file(key)["문서"]
|
||||
mine = _basis(doc, body)
|
||||
before = current(doc)
|
||||
job = body.get("할일") or None
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
* M02_MasterTemplete_Labor_RowModal.ts
|
||||
* 일위대가표 줄 누름 모달(PLAN 723-2 · 723-3 · 요구서_내역16 2장 4 · 5) — 일위대가표에서 빠진 줄 동작을 모달 안으로.
|
||||
* · 로직 줄(단가산출 문서 없음) · 셈 주입 SK 줄 = 머리(이름 · 「1 ㎡ 당」) ·
|
||||
* 단가산출 상세 줄 모드(717-2b · `Labor_RowSk` · 보기 고르기 = 모달 머리 · 옛 [항목 추가] · [율 줄 추가] 없음) ·
|
||||
* 단가산출 상세 줄 모드(717-2b · `Labor_RowSk` · 수식 보기 하나(836-3) · 옛 [항목 추가] · [율 줄 추가] 없음) ·
|
||||
* 로직 자재(바꾸기 · 기본 · 세 칸 · `Labor_LogicMats`)
|
||||
* · [적용] = 그 줄 덮개(M02 변경 항목) + 옵션(상세 옵션 칸 → 그 줄 입력값)
|
||||
* · 808-1(요구 701-4 · 5) — 옛 [옵션] · [단가산출로도 저장] · [항목 추가] · [율 줄 추가] 걷음 · 일위대가 줄 = 저장 때
|
||||
* M01 로직 줄 + 고침으로 펼쳐 담김(740-1 · `본뜬` = 온 단가산출) → 수식 문서 없음 = 수식 탭 · 보기 고르기 없이 단가산출 상세만 +
|
||||
* M01 로직 줄 + 고침으로 펼쳐 담김(740-1 · `본뜬` = 온 단가산출) → 수식 문서 없음 = 수식 보기 없이 단가산출 상세 몸만 +
|
||||
* 안내(온 단가산출 이름 · 수식은 그 단가산출에서 고쳐 다시 담음)
|
||||
* · 사용자 자재 줄 = 자재 모달(세 칸 · `Labor_MatTable` 한 줄)
|
||||
* · 고침 = 그 줄 화면 캐시(저장은 [저장]) · 금액은 표가 다시 셈 → `watch` 로 받아 다시 그림
|
||||
@@ -128,7 +128,7 @@ export function openRowModal(o: RowModalOptions): void {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 단가산출 상세 줄 모드(723-3) — 셈 주입(프로젝트 복사본)엔 없음 · 보기 고르기 = 모달 머리(808-1)
|
||||
// 단가산출 상세 줄 모드(723-3) — 셈 주입(프로젝트 복사본)엔 없음 · 수식 단추 = 모달 머리(808-1)
|
||||
const slot = el("span", { className: "m02-main__tools" });
|
||||
const skHost = el("div", { attrs: { "data-row-sk-host": "" } });
|
||||
let sk: RowSkHandle | null = null;
|
||||
@@ -185,7 +185,7 @@ export function openRowModal(o: RowModalOptions): void {
|
||||
closeLabel: tc("In_Close"),
|
||||
width: "1100px",
|
||||
wide: true,
|
||||
actions: row.참조 ? [slot, ...tools] : tools, // 로직 줄 = 보기 고르기 없음(상세만)
|
||||
actions: row.참조 ? [slot, ...tools] : tools, // 로직 줄 = 머리 단추 없음(상세만)
|
||||
beforeClose: async () => {
|
||||
if (sk?.take() && !(await askConfirm({ message: t("Discard"), danger: true }))) return false;
|
||||
finish();
|
||||
|
||||
@@ -116,6 +116,11 @@ export function mountSangeunDetail(
|
||||
scope,
|
||||
onSaved: (v) => void (refreshing = formulaSaved(v)),
|
||||
page: opt.source ? () => report() : undefined, // 804-8 페이지 [저장] · [되돌림]
|
||||
// 836-5 저장 전 가져온 식(불러오기 미리 보기) = 그 문서째 수식 보기
|
||||
memory: () =>
|
||||
doc.가져온 && !/^SK-(?!0{6})\d{6}$/.test(doc.키 ?? "")
|
||||
? (doc as Record<string, unknown>)
|
||||
: null,
|
||||
});
|
||||
/** 수식 탭 저장 = 파일 판 · `수식` 칸이 바뀜 — 페이지 판 · 이 화면 문서 `수식` 을 새로(상세 탭 셈이 씀) */
|
||||
async function formulaSaved(version: string): Promise<void> {
|
||||
@@ -198,7 +203,7 @@ export function mountSangeunDetail(
|
||||
: [
|
||||
el("div", {
|
||||
className: "m02-sk__head",
|
||||
children: [tabs.picker, tabs.tools, addButton, rateButton],
|
||||
children: [tabs.tools, addButton, rateButton],
|
||||
}),
|
||||
]),
|
||||
tabs.host,
|
||||
@@ -207,7 +212,7 @@ export function mountSangeunDetail(
|
||||
});
|
||||
host.replaceChildren(side ? side.wrap(main) : main);
|
||||
opt.slot?.replaceChildren(
|
||||
el("span", { className: "ui-toolbar m02-main__tools", children: [tabs.picker, tabs.tools] }),
|
||||
el("span", { className: "ui-toolbar m02-main__tools", children: [tabs.tools] }),
|
||||
);
|
||||
|
||||
const opened = (): boolean => (doc.열린입력 ?? []).length > 0;
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Sangeun_Tabs.ts
|
||||
* M02 단가산출(SK) 상세 보기 고르기(PLAN 799-2 · 804-2 · 804-6 · 요구 686-1) — 수식(첫 탭 · 줄 쌓기) · 단가산출 상세.
|
||||
* M02 단가산출(SK) 상세 보기(PLAN 799-2 · 804-2 · 804-6 · 836-3) — 수식(줄 쌓기) 하나 · 보기 고르기 단추 없음.
|
||||
* 수식 = 공용 줄 쌓기 화면(`A00_Common/line_stack_screen.ts` · M01 과 같은 꼴) — 길 앞머리만 M02 사본 길
|
||||
* (`/m02/layers/{층}/sangeun/{키}/doc` · 802-4a) · 층 쓰기 권한 없으면 읽기만
|
||||
* 저장(804-8 · `page` 있음 = 단가산출 화면) = 페이지 [저장] · [되돌림] 한 번에 — 탭 위 저장 단추 없음(M01 과 같음)
|
||||
* 옛 수식 문서 탭(802-4b-2)은 804-6 에 걷음 · 단가산출 상세 = 상세가 쥔 `editor`(수식일 때 숨김)
|
||||
* 수식 문서 없는 줄(일위대가 줄 모달 등) = 상세가 쥔 `editor`(단가산출 상세 몸)만 보임 — 빈 화면 없음
|
||||
* 저장 안 한 SK(일위대가 줄 모달 · 새 SK) = 「저장한 단가산출만」 글
|
||||
* [단가 갱신] · [재생성](808-1 · 요구 701-3) = `tools` — 보기 고르기 옆 머리 줄(수식 보기일 때만)
|
||||
* [단가 갱신] · [재생성](808-1 · 요구 701-3) = `tools` — 머리 줄(수식 보기일 때만)
|
||||
* ========================================================================== */
|
||||
|
||||
import { createSegment } from "@ui/ui_template_segment";
|
||||
import { createStackScreen } from "../A00_Common/line_stack_screen";
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { tl } from "../M01_MasterData/M01_MasterData_UI_LogicLab_Text";
|
||||
import type { Layer } from "./M02_MasterTemplete_Api_Fetch";
|
||||
|
||||
type View = "stack" | "ho";
|
||||
|
||||
export interface SkEquationOptions {
|
||||
scope: { layer: Layer; owner: string | null };
|
||||
edit: boolean;
|
||||
@@ -24,6 +20,8 @@ export interface SkEquationOptions {
|
||||
onSaved: (version: string) => void;
|
||||
/** 페이지가 저장 · 되돌림을 맡음(804-8) — 수식 고침 있음이 바뀔 때 부름 */
|
||||
page?: () => void;
|
||||
/** 저장 전 불러온 단가산출 문서(836-5 · 미리 보기 835-1) — 있으면 그 문서째 수식 보기 · 없으면 null */
|
||||
memory?: () => Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
/** SK 수식 길 — 꼬리 물음(`/table?key=…`)에 주인 물음을 이어 붙임 */
|
||||
@@ -36,13 +34,12 @@ export function skDocPath(scope: SkEquationOptions["scope"], key: string, path:
|
||||
return ask ? `${at}?${ask}` : at;
|
||||
}
|
||||
|
||||
/** 보기 고르기 · 수식 그릇 — `editor` = 단가산출 상세 그릇(수식일 때 숨김) */
|
||||
/** 수식 그릇 — `editor` = 단가산출 상세 그릇(수식 문서 있을 때 숨김) */
|
||||
export function createSangeunTabs(
|
||||
editor: HTMLElement,
|
||||
opt: SkEquationOptions,
|
||||
): {
|
||||
picker: HTMLElement;
|
||||
/** 수식 화면 단추([단가 갱신] · [재생성]) — 보기 고르기 옆(머리 줄) */
|
||||
/** 수식 화면 단추([단가 갱신] · [재생성]) — 머리 줄 */
|
||||
tools: HTMLElement;
|
||||
host: HTMLElement;
|
||||
/** 저장한 SK 키(빈 키 = 저장 전) */
|
||||
@@ -61,42 +58,22 @@ export function createSangeunTabs(
|
||||
scope: opt.scope, // 804-4 다른 단가산출 · 일위대가 복사
|
||||
page: opt.page,
|
||||
tools,
|
||||
memory: opt.memory,
|
||||
});
|
||||
let key = "";
|
||||
let view: View = "stack";
|
||||
const show = (v: View): void => {
|
||||
view = v;
|
||||
stack.root.hidden = tools.hidden = v !== "stack";
|
||||
editor.hidden = v !== "ho";
|
||||
if (!editor.hidden) editor.dataset.view = v;
|
||||
const show = (doc: boolean): void => {
|
||||
stack.root.hidden = tools.hidden = !doc;
|
||||
editor.hidden = doc;
|
||||
if (!doc) editor.dataset.view = "ho";
|
||||
};
|
||||
const picker = createSegment({
|
||||
options: [
|
||||
{ value: "stack", label: tl("View_Equation") },
|
||||
{ value: "ho", label: tl("Ho_Title") },
|
||||
],
|
||||
value: "stack",
|
||||
size: 32,
|
||||
ariaLabel: tl("View_Label"),
|
||||
onChange: (v) => {
|
||||
show(v as View);
|
||||
if (v === "stack") stack.draw(key, true);
|
||||
},
|
||||
});
|
||||
picker.root.dataset.skViews = "";
|
||||
show("stack");
|
||||
show(true);
|
||||
return {
|
||||
picker: picker.root,
|
||||
tools,
|
||||
host: el("div", { className: "m02-sk__views", children: [stack.root] }),
|
||||
draw: (k: string) => {
|
||||
key = k;
|
||||
// 문서 없는 줄(일위대가 로직 줄 모달 등 · 페이지 아님) = 단가산출 상세부터(808-1 · 수식은 문서가 있어야)
|
||||
if (!k && !opt.page && view === "stack") {
|
||||
picker.set("ho");
|
||||
return show("ho");
|
||||
}
|
||||
if (view === "stack") stack.draw(k);
|
||||
// 문서 없는 줄(일위대가 로직 줄 모달 등 · 페이지 아님) = 단가산출 상세 몸만(808-1 · 수식은 문서가 있어야)
|
||||
const doc = !!k || !!opt.page || !!opt.memory?.();
|
||||
show(doc);
|
||||
if (doc) stack.draw(k);
|
||||
},
|
||||
canLeave: () => stack.canLeave(),
|
||||
dirty: () => stack.dirty(),
|
||||
|
||||
@@ -194,7 +194,6 @@ def test_screen_same_part_first_tab():
|
||||
root = Path(__file__).resolve().parents[2] / "M02_MasterTemplete"
|
||||
assert not (root / "M02_MasterTemplete_Sangeun_Equation.ts").exists()
|
||||
tabs = (root / "M02_MasterTemplete_Sangeun_Tabs.ts").read_text(encoding="utf-8")
|
||||
views = re.findall(r'\{ value: "(\w+)", label: ', tabs)
|
||||
# 799-2 — 시트 · 인력·재료·경비 · 시험 계산 · 텍스트 수식 걷음
|
||||
assert views == ["equation", "ho"]
|
||||
assert 'show("equation");' in tabs
|
||||
# 799-2 · 836-3 — 시트 · 인력·재료·경비 · 시험 계산 · 텍스트 수식 · 보기 고르기(단가산출 상세 탭) 걷음
|
||||
assert not re.findall(r'\{ value: "(\w+)", label: ', tabs) and "createSegment" not in tabs
|
||||
assert "show(true);" in tabs
|
||||
|
||||
@@ -56,11 +56,13 @@ def test_걷은_파일_없고_부르는_곳_없음():
|
||||
)
|
||||
|
||||
|
||||
def test_보기는_수식_단가산출상세_둘():
|
||||
def test_보기는_수식_하나_고르기_없음():
|
||||
m01 = _text(M01 / "M01_MasterData_UI_LogicLab_View.ts")
|
||||
m02 = _text(M02 / "M02_MasterTemplete_Sangeun_Tabs.ts")
|
||||
for src in (m01, m02):
|
||||
assert re.findall(r'\{ value: "(\w+)", label: ', src) == ["stack", "ho"] # 804-6
|
||||
assert not re.findall(r'\{ value: "(\w+)", label: ', src) and "createSegment" not in src
|
||||
assert "createViewPicker" not in _text(M01 / "M01_MasterData_UI_LogicLab.ts") # 836-3
|
||||
assert 'editor.dataset.view = "stack";' in _text(M01 / "M01_MasterData_UI_LogicLab.ts")
|
||||
assert 'export type LabView = "stack" | "ho";' in m01
|
||||
css = _text(M01 / "M01_MasterData_UI_LogicLab_Style.css")
|
||||
assert '[data-view="ho"] [data-block]:not([data-block="ho"])' in css
|
||||
|
||||
@@ -52,7 +52,7 @@ def test_길_앞머리_M02_공용화면():
|
||||
assert "/m02/layers/${scope.layer}/sangeun/${encodeURIComponent(key)}/doc${tail}" in tabs
|
||||
assert "admin: opt.edit" in tabs # 층 쓰기 권한 = 고침
|
||||
scr = _src("A00_Common/line_stack_screen.ts") # 804-6 수식 탭 = 줄 쌓기
|
||||
assert "cp ? cp.route(key, path) : opt.base + path" in scr
|
||||
assert 'cp ? cp.route(saved ? "-" : key, path) : opt.base + path' in scr # 836-5 저장 전 문서 = 길 키 「-」
|
||||
assert "{ 문서: doc, 끊음: cut, 판: version }" in scr # 저장 몸
|
||||
detail = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts")
|
||||
assert (
|
||||
|
||||
@@ -240,9 +240,7 @@ def test_화면_이음():
|
||||
'base: "/m01/logic/doc",' in lab and 'cache: "m01_doc_drafts",' in lab
|
||||
) # 804-7 page = 페이지 [저장]
|
||||
assert "stack: stack.root" in lab and "=> stack.canLeave();" in lab # 804-6 수식 탭 = 줄 쌓기
|
||||
assert '{ value: "stack", label: tl("View_Equation") }' in _src(
|
||||
"M01_MasterData/M01_MasterData_UI_LogicLab_View.ts"
|
||||
)
|
||||
assert 'editor.dataset.view = "stack";' in lab # 836-3 보기 고르기 없음 · 수식 하나
|
||||
assert '[data-view="stack"]' in _src("M01_MasterData/M01_MasterData_UI_LogicLab_Style.css")
|
||||
tabs = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Tabs.ts")
|
||||
assert 'cache: "m02_sk_doc_drafts"' in tabs and tabs.count("skDocPath(opt.scope, k, p)") == 1
|
||||
|
||||
@@ -56,8 +56,7 @@ def test_보기_수식은_줄쌓기_첫탭():
|
||||
"M02_MasterTemplete/M02_MasterTemplete_Sangeun_Tabs.ts",
|
||||
):
|
||||
src = _text(rel)
|
||||
assert '{ value: "stack", label: tl("View_Equation") },' in src, rel
|
||||
assert 'value: "stack",\n' in src and '"equation"' not in src, rel
|
||||
assert "createSegment" not in src and '"equation"' not in src, rel # 836-3 고르기 없음
|
||||
lab = _text("M01_MasterData/M01_MasterData_UI_LogicLab.ts")
|
||||
assert "createStackScreen({" in lab and "createDocScreen" not in lab
|
||||
|
||||
|
||||
@@ -340,7 +340,7 @@ def test_808_1_수량_칸_식_글_꼬리표_글꼴_머리():
|
||||
assert "opt.tools?.replaceChildren(...tools);" in screen
|
||||
assert "children: opt.tools ? [] : tools" in screen
|
||||
tabs = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Tabs.ts")
|
||||
assert 'stack.root.hidden = tools.hidden = v !== "stack";' in tabs
|
||||
assert "stack.root.hidden = tools.hidden = !doc;" in tabs
|
||||
assert 'Reset: ["M01 값으로", "To M01"]' in _src(
|
||||
"M02_MasterTemplete/M02_MasterTemplete_Main_Actions.ts"
|
||||
)
|
||||
|
||||
@@ -134,7 +134,7 @@ def test_808_1_모달_머리_일원화():
|
||||
assert "slot: o.slot," in _src("M02_MasterTemplete_Labor_RowSk.ts")
|
||||
detail = _src("M02_MasterTemplete_Sangeun_Detail.ts")
|
||||
assert "...(opt.slot\n ? []" in detail # 머리 칸 있으면 옛 단추 줄 없음
|
||||
assert "children: [tabs.picker, tabs.tools]" in detail
|
||||
assert "children: [tabs.tools]" in detail and "tabs.picker" not in detail
|
||||
assert r"/^SK-(?!0{6})\d{6}$/" in detail
|
||||
tabs = _src("M02_MasterTemplete_Sangeun_Tabs.ts")
|
||||
assert 'if (!k && !opt.page && view === "stack")' in tabs
|
||||
assert "const doc = !!k || !!opt.page || !!opt.memory?.();" in tabs # 836-3 · 836-5 문서 없는 줄 = 상세 몸만
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
||||
"""836-5 저장 전 단가산출(불러오기 미리 보기)도 수식 보기 — 임시 storage · M01 그대로.
|
||||
|
||||
① 미리 보기 문서째 수식 길 읽기(`Sk_Path.read_memory`) = 디스크 쓰기 0 · 금액 = 저장한 SK 를 읽은 값 · 미리 셈(`본`)도 쓰기 0
|
||||
② 화면 — 줄 쌓기가 빈 키 + 저장 전 문서를 서버에 문서째 읽힘 · 왼쪽은 보기만 · 탭은 그 문서가 있을 때만 수식
|
||||
실무 엑셀 없으면 ① skip.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import helper_m02_hopyo_golden as golden
|
||||
import pytest
|
||||
|
||||
from M02_MasterTemplete import M02_Doc_Sk_Path as path
|
||||
from M02_MasterTemplete import M02_Hopyo_Excel as hx
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Sangeun_FromHopyo as fh
|
||||
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _files(folder: Path) -> dict[str, int]:
|
||||
return {str(p.relative_to(folder)): p.stat().st_mtime_ns for p in folder.rglob("*") if p.is_file()} # fmt: skip
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("tag", "no"), [("봉화", 3), ("북부청임도", 2)]
|
||||
) # STmate 원파일 · 엑셀(EST)
|
||||
def test_memory_doc_read_writes_nothing_same_as_saved(world, tag, no) -> None:
|
||||
src = golden.FILES.get(tag)
|
||||
if src is None or not src.exists():
|
||||
pytest.skip(f"실무 엑셀 없음 {tag}")
|
||||
folder, data = world["personal"], src.read_bytes()
|
||||
with store.at(folder):
|
||||
parsed = hx.read_book(src.name, data)
|
||||
row = hs.save(folder, src.name, parsed, {"이름": "sub1임시"}, None, hs.fingerprint(data))
|
||||
fh.keep(folder, row["키"], src.name, data)
|
||||
with store.at(folder):
|
||||
seen = fh.draft(folder, row["키"], no)
|
||||
before = _files(folder)
|
||||
got = path.read_memory(seen["문서"]) # 저장 전 문서째
|
||||
asked = path.preview("-", {"본": seen["문서"], "문서": got["문서"]}) # 미리 셈
|
||||
assert _files(folder) == before # 읽기 · 미리 셈 = 디스크 쓰기 0
|
||||
assert got["판"] == "" and got["셈"] and asked["셈"] == got["셈"]
|
||||
made = fh.make(folder, row["키"], no, doc=seen["문서"]) # [저장]
|
||||
saved = path.read(made["이름"])
|
||||
assert saved["셈"] == got["셈"] # 금액 = 저장한 SK 와 같음
|
||||
with pytest.raises(store.StoreError):
|
||||
path.read_memory("문서 아님")
|
||||
|
||||
|
||||
def test_screen_memory_doc_stack_view() -> None:
|
||||
stack = (ROOT / "A00_Common" / "line_stack_screen.ts").read_text(encoding="utf-8")
|
||||
assert "memory?: () => Record<string, unknown> | null;" in stack
|
||||
assert 'cp.route(saved ? "-" : key, path)' in stack and "본: saved" in stack
|
||||
assert 'call<Loaded>("/read", {})' in stack and "left.inert = !!mem();" in stack
|
||||
tabs = (ROOT / "M02_MasterTemplete" / "M02_MasterTemplete_Sangeun_Tabs.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert (
|
||||
"const doc = !!k || !!opt.page || !!opt.memory?.();" in tabs
|
||||
and "memory: opt.memory," in tabs
|
||||
)
|
||||
detail = (ROOT / "M02_MasterTemplete" / "M02_MasterTemplete_Sangeun_Detail.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "doc.가져온 &&" in detail and "memory: () =>" in detail
|
||||
router = (ROOT / "M02_MasterTemplete" / "M02_Doc_Router.py").read_text(encoding="utf-8")
|
||||
assert '"/layers/{layer}/sangeun/-/doc/read"' in router and "write=body.본 is None" in router
|
||||
Reference in New Issue
Block a user