Merge remote-tracking branch 'origin/dev' into sub_laptop_6

This commit is contained in:
2026-11-01 15:35:33 +09:00
18 changed files with 841 additions and 116 deletions
+103
View File
@@ -0,0 +1,103 @@
"""M02 일위대가 불러오기 → 단가산출(PLAN 718-1 · 요구서 내역12 4장 22) —
산근(단산) 블록 = 바탕 없는 단가산출.
블록마다 SK 하나 — 그 블록을 부르는 첫 엑셀 줄(「단산N참조」)의 1 단위 단가
(노무비 · 재료비 · 경비)를 고정 더한줄 하나로(`M02_Hopyo_Fixed` 꼴 · 엑셀 숫자 그대로 ·
서버가 파일에서 읽음) · 이름 = 블록 이름(없으면 줄 품명) · 단위 = 줄 단위(없으면 블록 · 「식」) ·
구분 「실무자료」 · 상세구분 = 파일 · 본뜬 = {층 「실무자료」, 키 「HP-###### 단산N」, 파일, 원본,
확인} · 같은 본뜬이 이 층에 있으면 그대로(겹침 없음) · 만드는 층 = 부르는 층(마법사에서 고른 층).
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from common_util.common_util_time import utc_now_text
from M02_MasterTemplete import M02_Hopyo_Fixed as hfix
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
from M02_MasterTemplete import M02_Labor_Fixed as fixed
from M02_MasterTemplete import M02_Labor_Ref as refs
ORIGIN = "실무자료"
KIND = "sangeun"
def mark(key: str, no: int) -> str:
return f"{key} 단산{no}"
def made(folder: str | Path, key: str | None = None) -> dict[str, str]:
"""이 층 실무자료 단가산출 `{본뜬 키: SK 키}`(`key` = 그 HP 것만)."""
out = {}
for path in sorted((Path(folder) / KIND).glob("SK-*.json")):
try:
got = json.loads(path.read_bytes()).get("본뜬")
except (OSError, ValueError, AttributeError):
continue
if isinstance(got, dict) and got.get("층") == ORIGIN:
if key is None or str(got.get("키") or "").startswith(f"{key} "):
out[str(got.get("키"))] = path.stem
return out
def _doc(line: dict, block: dict, no: int, hp: dict, stamp: str, who: Any) -> dict[str, Any]:
name = str(block.get("이름") or line.get("이름") or "").strip() or f"단산{no}"
unit = str(line.get("단위") or block.get("단위") or "").strip() or "식"
spec = str(line.get("규격") or "").strip()
price = {k: hfix._plain(v) for k, v in hfix._parts(line.get("단가")).items()}
fx = {
"이름": name,
"규격": spec,
"단위": unit,
"단가": price,
"이음": dict.fromkeys(fixed.LINKS),
"출처": {"파일": hp.get("파일"), "호표": line.get("_호표"), "줄": int(line.get("차례", 0))},
}
return {
"종류": "단가산출",
"이름": name,
"단위": unit,
"구분": ORIGIN,
"상세구분": str(hp.get("파일") or ""),
"본뜬": {
"층": ORIGIN,
"키": mark(hp["키"], no),
"파일": hp.get("파일"),
"원본": list(refs.here()),
"확인": {"누가": who, "때": stamp},
},
"바탕": None,
"입력값": {},
"열린입력": [],
"고름": {},
"자재표시": {},
"덮개": {},
"더한줄": [{"고정": fx, "이름": name, "단위": unit, "수량": 1}],
"뺀줄": [],
}
def build(folder: Path, key: str, who: Any = None) -> dict[str, Any]:
"""[단가산출 만들기] — 산근 블록마다 바탕 없는 단가산출 →
`{블록, 만듦 [{번, 이름, 키}], 그대로}`."""
hp = hopyo_store.read(folder, key)
blocks = {int(b["번"]): b for b in hp.get("단산") or [] if b.get("번") is not None}
calls: dict[int, dict] = {}
for h in hp.get("호표") or []:
for line in h.get("줄") or []:
ref = line.get("참조") or {}
no = int(ref.get("번") or 0) if ref.get("종류") == "단산" else 0
if no in blocks and no not in calls:
calls[no] = {**line, "_호표": h.get("번")}
have, stamp = made(folder, key), utc_now_text()
out: dict[str, Any] = {"블록": len(calls), "만듦": [], "그대로": 0}
for no in sorted(calls):
if mark(key, no) in have:
out["그대로"] += 1
continue
doc = _doc(calls[no], blocks[no], no, hp, stamp, who)
got = refs.create(KIND, doc)
out["만듦"].append({"번": no, "이름": doc["이름"], "키": got["이름"]})
return out
+12 -3
View File
@@ -44,7 +44,7 @@ def _texts(row: dict) -> list[str]:
out.append(row["결과"]["식"])
for item in row.get("호표", []):
out.append(item["수량"])
if not isinstance(item.get("요소"), dict):
if isinstance(item.get("요소"), str): # 단가산출 참조 · 고정 더한줄 = 요소 없음(719-1)
out.append(item["요소"])
out += [extra["식"] for extra in row.get("덧줄", [])]
return out
@@ -200,6 +200,10 @@ class _Collector:
return
self.logics.add(key)
self.keys.add(key)
self.row(row)
def row(self, row: dict) -> None:
"""로직 줄 하나가 닿는 것 — 마스터 로직 · 굳힌 단가산출 줄(719-1 · 마스터 밖 줄)."""
if isinstance(row.get("흐름"), dict): # 흐름 로직 — 옛 칸 대신 흐름이 쓰는 것을 모음
self.flow(row)
return
@@ -221,12 +225,17 @@ class _Collector:
self.pick(row, item["요소"], item.get("단위"))
def collect(logic_keys: list[str], materials: list[str] | None = None) -> dict[str, Any]:
"""담은 로직 + 자재(MT)의 닫힌 단가 묶음 — `{파일 이름: 파일(줄은 모은 것만 · 차례 그대로)}`."""
def collect(
logic_keys: list[str], materials: list[str] | None = None, extra: list[dict] | None = None
) -> dict[str, Any]:
"""담은 로직 + 자재(MT)의 닫힌 단가 묶음 — `{파일 이름: 파일(줄은 모은 것만 · 차례 그대로)}` ·
`extra` = 마스터 밖 로직 줄(굳힌 단가산출 719-1) — 그 줄이 닿는 것만 모음(줄은 안 담음)."""
files, whole = master()
collector = _Collector(whole)
for key in logic_keys:
collector.logic(key)
for row in extra or []:
collector.row(row)
for code in materials or []:
collector.add(code)
out: dict[str, Any] = {}
+102
View File
@@ -0,0 +1,102 @@
"""프로젝트 사본 — M02 단가산출(SK) 풀어 굳힘(PLAN 719-1 · 요구서 내역12 원칙 3 · 4장 14).
사본을 만들 때(구조물 사본 `M02_Item_Source.build_source` · B09 서고 사본 `Bill_Library`) 굳힌
참조사본 안 SK 문서마다 고친 줄(`M02_Sangeun_Calc.resolve` = 바탕 사슬 + 덮개 · 더한줄 · 뺀줄)을
풀어 사본 `단가.json` 로직 줄 하나로 굳힘 — 사본 SK 문서는 그 줄만 바탕으로 가리킴(덮개 0 ·
연결 끊김). 셈은 그대로 `M02_Sangeun_Calc` 한 벌(사본 색인 = 사본 `단가.json`).
굳힌 키 = SK 참조 이름표(`ref_id` = 층 · 주인 · 키)에서 정해지는 `GX9#####`(해시 · 겹치면
이름표 차례로 다음 번호) — 같은 SK = 같은 굳힌 키([업데이트] 때 B09 덮개 cover · cid · 입력값 ·
고른자재가 짝을 안 잃음). 원본 층 문서는 읽기만.
사본 SK 문서 = {종류 · 키 · 이름 · 단위 · 구분 · 상세구분, 바탕 {로직: 굳힌 키}, 입력값(사슬 합),
열린입력, 켜쌓기율, 고름 · 자재표시(굳힌 줄 cid), 덮개 {}, 더한줄 [], 뺀줄 [],
굳힌줄(= 단가.json 줄), 원본(원래 SK 문서 · 「M02 에서 고침」 근거), 바탕판, 짝잃음}.
단가산출 속 일위대가(SC) · SK 더한줄 = 참조 줄 그대로 → 사본 셈이 굳힌 참조사본으로 펼침.
"""
from __future__ import annotations
import hashlib
from typing import Any
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_Sangeun_Doc as sd
#: 사본 `단가.json` 안 굳힌 SK 줄 파일(`master_formula.Master` 가 그룹 「로직」 으로 읽음)
FILE = "로직_단가산출.json"
ROW = "굳힌줄"
_HEAD, _LOW, _SPAN = "GX", 900000, 99999 # GX900000 ~ GX999998(999999 = M01 저장 전 초안)
def frozen_key(rid: str, taken: set[str]) -> str:
"""SK 이름표 → 굳힌 로직 키 — 해시 자리 · 이미 쓴 키면 다음 번호(제자리 `taken` 에 더함)."""
at = int(hashlib.sha256(rid.encode("utf-8")).hexdigest(), 16) % _SPAN
while (key := f"{_HEAD}{_LOW + at:06d}") in taken:
at = (at + 1) % _SPAN
taken.add(key)
return key
def is_sangeun(doc: Any) -> bool:
return isinstance(doc, dict) and doc.get("종류") == sd.TITLE
def freeze(copies: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
"""참조사본 `{ref_id: 문서}` → SK 를 풀어 굳힌 참조사본(새 묶음 · 받은 것 안 고침) ·
못 푼 SK(없는 바탕 · 순환) = 그대로(사본 셈이 까닭을 냄)."""
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Item_Prices as prices
from M02_MasterTemplete import M02_Sangeun_Calc as calc
sks = sorted(rid for rid, doc in copies.items() if is_sangeun(doc))
if not sks:
return copies
whole = m01.loaded()[1]
def read(ref: dict) -> dict | None:
return copies.get(refs.ref_id(ref))
out, taken = dict(copies), set()
for rid in sks:
doc = copies[rid]
try:
got = calc.resolve(doc, whole, read, (rid,))
except sd.DocError:
continue
key = frozen_key(rid, taken)
row = prices.to_json({**got["줄"], "키": key})
ids = sd.line_ids(row)
frozen = {k: doc.get(k) for k in ("종류", "키", "이름", "단위", "구분", "상세구분")}
frozen |= {
"바탕": {"로직": key},
"입력값": prices.to_json(got["입력값"]),
"열린입력": list(doc.get("열린입력") or []),
"고름": {ids[int(n)]: mt for n, mt in got["고름"].items()},
"자재표시": {ids[int(n)]: one for n, one in got["자재표시"].items()},
"덮개": {},
"더한줄": [],
"뺀줄": [],
ROW: row,
"원본": doc,
"바탕판": got["바탕판"],
"짝잃음": got["짝잃음"],
}
if got["켜쌓기율"] is not None:
frozen["켜쌓기율"] = prices.to_json(got["켜쌓기율"])
out[rid] = frozen
return out
def rows(copies: dict[str, Any]) -> list[dict[str, Any]]:
"""굳힌 SK 줄 — 사본 `단가.json` `FILE` 에 넣음(이름표 차례)."""
return [doc[ROW] for _, doc in sorted(copies.items()) if is_sangeun(doc) and ROW in doc]
def materials(copies: dict[str, Any]) -> list[str]:
"""굳힌 SK 고른 자재 키 — 단가 묶음에 담음."""
return [
str(mt)
for doc in copies.values()
if is_sangeun(doc)
for mt in (doc.get("고름") or {}).values()
]
+13 -4
View File
@@ -5,7 +5,7 @@
`일위대가.json` = 층 `labor/<열id>.json` 문서(39-1c · 없으면 옛 UA 조합 줄 ·
규격 열 = summary_item `spec` 열 수량을 손으로 굳힘 · 704-4)
+ 담은 로직 줄(M01 시스템) + 알약(가져온 때 그대로) +
참조사본(단가승격 · 사전조합을 값으로 굳힘) ·
참조사본(단가승격 · 사전조합을 값으로 굳힘 · 단가산출 SK = 풀어 굳힌 로직 줄 719-1) ·
`단가.json` 계산에 닿는 마스터 줄 전부(담은 로직 + 일위대가 자재 MT)
`원본판` = 모은 내용의 해시 — 지금 층 · 마스터에서 다시 모은 판과 대어 「마스터가 바뀜」 을 앎.
프로젝트가 고치는 값(`입력값.json` · `고른자재.json`)은 원본이 아니라 여기서 안 모음.
@@ -93,11 +93,12 @@ def labor_rows(ua_key: str) -> dict[str, Any] | None:
def labor_doc_rows(doc: dict[str, Any]) -> dict[str, Any]:
"""일위대가 문서(39-1a) + 담은 로직 줄 + 알약(`M02_Labor_Doc.pills` · 가져온 때 그대로 —
복사본 계산이 마스터를 안 읽게) + 참조사본 `{ref_id: 문서}`(50-3 · 원본이 바뀌어도 그대로)."""
from M02_MasterTemplete import M02_Item_Sangeun as sk
from M02_MasterTemplete import M02_Labor_Doc as labor_doc
from M02_MasterTemplete import M02_Labor_Ref as refs
rows = {**logic_rows(doc), "알약": labor_doc.pills(doc)}
copies = refs.frozen(doc)
copies = sk.freeze(refs.frozen(doc)) # 단가산출(SK) = 풀어 굳힌 로직 줄(719-1)
return {**rows, "참조사본": copies} if copies else rows
@@ -162,11 +163,14 @@ def price_bundle(labor: dict[str, Any] | None, root: Path) -> dict[str, Any]:
"""사본 단가 묶음(`단가.json`) — 담은 로직 · 굳힌 참조 로직 · 자재 · 고른 자재 · 사전조합 자재 ·
개인 자재(`root` = 원본 층 · B09 설계내역서 서고 사본도 이 길 · 204-2)."""
from M02_MasterTemplete import M02_Item_Prices as prices
from M02_MasterTemplete import M02_Item_Sangeun as sk
from M02_MasterTemplete import M02_Labor_Fixed as fixed
from M02_MasterTemplete import M02_Labor_Ref as refs
copies = (labor or {}).get("참조사본") or {}
sk_rows = sk.rows(copies) # 굳힌 단가산출 줄(719-1) — 그 줄이 닿는 마스터 줄도 모음
logic_keys = [str(one.get("키")) for one in (labor or {}).get("로직", [])]
logic_keys += refs.frozen_logics((labor or {}).get("참조사본") or {}) # 굳힌 단가승격 로직
logic_keys += refs.frozen_logics(copies) # 굳힌 단가승격 로직
doc = (labor or {}).get("줄") or {}
materials = [str(m.get("코드")) for m in doc.get("자재") or []]
materials += [ # 고른 로직 자재(103-1) 품목 줄도 — 복사본 계산이 그 단가를 씀
@@ -174,8 +178,13 @@ def price_bundle(labor: dict[str, Any] | None, root: Path) -> dict[str, Any]:
]
materials += refs.frozen_materials((labor or {}).get("참조사본") or {}) # 175-2 사전조합 자재
materials += fixed.link_keys(doc) # 705-1 고정 줄이 잇는 노임 · 자재 · 기계
materials += sk.materials(copies)
wanted = logic_keys + [code for code in materials if code]
bundle = prices.to_json(prices.collect(logic_keys, materials)) if wanted else {}
bundle = (
prices.to_json(prices.collect(logic_keys, materials, sk_rows)) if wanted or sk_rows else {}
)
if sk_rows:
bundle[sk.FILE] = {"그룹": "로직", "원문": "자체", "줄": sk_rows}
if mine := own.bundle(
[c for c in materials if own.is_own(c)], root
): # 개인 자재(148-3 · 사전조합 것도 175-2) — 원본 목록이 바뀌어도 사본 그대로
+14 -1
View File
@@ -33,6 +33,7 @@ from M02_MasterTemplete import M02_Hopyo_Ledger as hopyo_ledger
from M02_MasterTemplete import M02_Hopyo_Match as hopyo_match
from M02_MasterTemplete import M02_Hopyo_Paste as hopyo_paste
from M02_MasterTemplete import M02_Hopyo_Relink as hopyo_relink
from M02_MasterTemplete import M02_Hopyo_Sangeun as hopyo_sangeun
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Ref as refs
@@ -229,7 +230,8 @@ def _upload(name: str, data: bytes, who: dict[str, Any]) -> dict[str, Any]:
when = str(old.get("끝낸때") or "")[:10]
raise store.StoreError(
409,
f"이미 불러온 엑셀 「{old.get('파일')}」({when} · 일위대가 {made}) — 다시 만들지 않음",
f"이미 불러온 엑셀 「{old.get('파일')}」({when} · 만든 일위대가 · 단가산출 {made})"
" — 다시 만들지 않음",
)
if same := next((r for r in hopyo_store.rows(store.root()) if r.get("지문") == mark), None):
return {**same, "이어서": True}
@@ -488,6 +490,7 @@ def _finish(folder: Path, key: str) -> dict[str, Any]:
made = sorted(
{sc for mark, sc in hopyo_build.confirmed(folder).items() if mark.startswith(head)}
)
made += sorted(hopyo_sangeun.made(folder, key).values()) # 단가산출(SK)
if (cache_path := _later("M02_Hopyo_Fit", "cache_path")) is not None:
try:
got = cache_path(folder, key)
@@ -500,6 +503,16 @@ def _finish(folder: Path, key: str) -> dict[str, Any]:
return {"지움": key, "만든": len(made)}
@router.post("/layers/{layer}/hopyo/{key}/sangeun")
def hopyo_make_sangeun(
layer: Layer, key: str, owner: str | None = None, session: Session = Depends(verify_session)
) -> dict:
"""불러오기 결과 「단가산출」(718-1) — 산근 블록마다 바탕 없는 단가산출(파일 층 = 이 층) →
`{블록, 만듦, 그대로}`."""
folder = _root(session, layer, owner, write=True)
return _call(folder, hopyo_sangeun.build, folder, key, _who(session))
class PasteBody(_Strict):
"""엑셀 붙여넣기(705-2) — `글` = 붙인 글 그대로 · `열` = 기억한 열 짝(없으면 머리 글 · 짐작 + 물음) ·
`줄만` = 열린 문서에 줄로 · `있음` = 문서의 고정 줄 가름(이름 겹침 피함)."""
@@ -1,11 +1,12 @@
/* =============================================================================
* M02_MasterTemplete_Hopyo_Wizard.ts
* 일위대가 불러오기 = 한시 마법사(PLAN 718-1 · 요구서 내역12 4장 22) — 본 칸 한 화면 · 걸음 셋:
* ① 결과 층(쓸 수 있는 층만 · 기본 개인) · 결과(일위대가 · 단가산출 = 715-2 뒤 켬) · 엑셀 고르기 → 올림
* ① 결과 층(쓸 수 있는 층만 · 기본 개인) · 결과(일위대가 늘 · 단가산출 체크) · 엑셀 고르기 → 올림
* (그 층에 둠 · 같은 엑셀 작업 중 = 그 파일 이어서 · 끝낸 같은 엑셀 = 서버 409 안내) · 작업 중 파일 이어서
* ② 보기 · 확인 · 만들기 = 엑셀 보기 창(`Hopyo_Sheet` — [모두 고정으로 들여오기] · 호표 [수정] →
* 확인 창 만들기 · [구조물 조합으로] 그대로) · 만들 층 = 고른 층
* ③ [끝내기] = 물음 → 서버가 이번 HP · 격자 · 맞춤셈 캐시 지움 · 만든 일위대가 · 별칭 장부 남음 · 지문 기록
* 확인 창 만들기 · [구조물 조합으로] 그대로) · 만들 층 = 고른 층 ·
* 단가산출 체크면 [단가산출 만들기] = 산근 블록마다 바탕 없는 단가산출(서버 `M02_Hopyo_Sangeun` · 겹침 없음)
* ③ [끝내기] = 물음 → 서버가 이번 HP · 격자 · 맞춤셈 캐시 지움 · 만든 일위대가 · 단가산출 · 별칭 장부 남음 · 지문 기록
* 작업 중 = 서버에 보관(떠났다 와도 ① 목록에서 이어서) · 옛 HP(지문 없음)는 서버 목록에서 숨김.
* ========================================================================== */
@@ -23,6 +24,7 @@ import {
type HopyoFile,
type HopyoScope,
} from "./M02_MasterTemplete_Hopyo_Api";
import { call } from "./M02_MasterTemplete_Api_Fetch";
import { accessFor, type ManageLayer } from "./M02_MasterTemplete_Layer";
const LAYERS: { layer: ManageLayer; text: string }[] = [
@@ -35,9 +37,6 @@ const STEPS = [
{ value: "2", label: "② 보기 · 확인 · 만들기" },
{ value: "3", label: "③ 끝내기" },
];
/** 단가산출 결과 — M02 단가산출 서고(715-2)가 들어오면 켬 */
export const SANGEUN_READY = false;
const SANGEUN_WHY = "M02 단가산출 칸(715-2)이 들어온 뒤 켬";
type Got = HopyoFile & { 이어서?: boolean };
const why = (error: unknown): string => (error instanceof Error ? error.message : String(error));
@@ -70,6 +69,7 @@ export function buildHopyoWizard(): HTMLElement {
});
let layers: ManageLayer[] = [];
let pick: ManageLayer = "personal";
let wantSangeun = false; // 결과 「단가산출」 체크
const scopeOf = (layer: ManageLayer): HopyoScope => ({ layer, owner: null });
async function first(): Promise<void> {
@@ -112,15 +112,17 @@ export function buildHopyoWizard(): HTMLElement {
});
seg.root.dataset.hopyoLayer = "";
const ilwi = createCheckbox("일위대가(고정 줄)", { checked: true, disabled: true });
const sangeun = createCheckbox("단가산출(산근 블록 · 바탕 없음)", { disabled: !SANGEUN_READY });
sangeun.root.title = SANGEUN_READY ? "" : SANGEUN_WHY;
const sangeun = createCheckbox("단가산출(산근 블록 · 바탕 없음)", {
checked: wantSangeun,
onChange: (on) => (wantSangeun = on),
});
sangeun.root.dataset.hopyoSangeun = "";
const busy = el("div", { className: "m02-hopyo" });
body.replaceChildren(
createText("결과 층", "muted", 14),
el("div", { className: "ui-toolbar", children: [seg.root] }),
createText("결과", "muted", 14),
el("div", { className: "ui-toolbar", children: [ilwi.root, sangeun.root] }),
...(SANGEUN_READY ? [] : [createText(`단가산출 — ${SANGEUN_WHY}`, "hint")]),
el("div", { className: "ui-toolbar", children: [upload, input] }),
createPanelHead({ title: "작업 중", level: 4, divider: true }).root,
busy,
@@ -169,6 +171,14 @@ export function buildHopyoWizard(): HTMLElement {
)
.catch((error: unknown) => showToast(why(error), "error")),
});
const sangeun = createButton({
label: "단가산출 만들기",
variant: "filled",
onClick: () => void makeSangeun(),
});
sangeun.title =
"엑셀 산근 블록마다 바탕 없는 단가산출(엑셀 단가 그대로 · 이미 만든 블록은 그대로)";
sangeun.dataset.hopyoSangeunMake = "";
const finish = createButton({ label: "끝내기", variant: "ghost", onClick: () => void end() });
finish.dataset.hopyoFinish = "";
const back = createButton({ label: "처음으로", variant: "link", onClick: () => void first() });
@@ -177,22 +187,44 @@ export function buildHopyoWizard(): HTMLElement {
body.replaceChildren(
createText(`${file.파일} · ${textOf(layer)} 층 · 호표 ${file.호표수}${wrong}`, "muted", 14),
createText(
"엑셀 보기 창에서 [모두 고정으로 들여오기] 또는 호표 [수정] → 확인하고 만들기 · [구조물 조합으로] · 만든 일위대가는 고른 층에 들어감",
"엑셀 보기 창에서 [모두 고정으로 들여오기] 또는 호표 [수정] → 확인하고 만들기 · [구조물 조합으로] · 만든 일위대가 · 단가산출은 고른 층에 들어감",
"hint",
),
el("div", { className: "ui-toolbar", children: [view, finish, back] }),
el("div", {
className: "ui-toolbar",
children: [view, ...(wantSangeun ? [sangeun] : []), finish, back],
}),
);
async function makeSangeun(): Promise<void> {
sangeun.disabled = true;
try {
const got = await call<{ 블록: number; 만듦: unknown[]; 그대로: number }>(
`/layers/${layer}/hopyo/${encodeURIComponent(file.키)}/sangeun`,
{ method: "POST" },
);
const kind = got.블록 ? "success" : "warning";
showToast(
`단가산출 ${got.만듦.length} 만듦 · 그대로 ${got.그대로} · 산근 블록 ${got.블록}`,
kind,
);
} catch (error) {
showToast(why(error), "error");
} finally {
sangeun.disabled = false;
}
}
async function end(): Promise<void> {
steps.set("3");
const ok = await showConfirmDialog(
`「${file.파일}」 불러오기를 끝냄 — 올린 파일(엑셀 결과 · 격자 · 맞춤 캐시)을 지움 · 만든 일위대가와 별칭 장부는 남음 · 일위대가를 만들었으면 같은 엑셀은 다시 올리지 못함`,
`「${file.파일}」 불러오기를 끝냄 — 올린 파일(엑셀 결과 · 격자 · 맞춤 캐시)을 지움 · 만든 일위대가 · 단가산출과 별칭 장부는 남음 · 하나라도 만들었으면 같은 엑셀은 다시 올리지 못함`,
"끝내기",
);
if (!ok) return steps.set("2");
try {
const got = (await deleteHopyo(scope, file.키)) as { 만든?: number };
showToast(`끝냄 — 만든 일위대가 ${got.만든 ?? 0} · 올린 파일 지움`, "success");
showToast(`끝냄 — 만든 일위대가 · 단가산출 ${got.만든 ?? 0} · 올린 파일 지움`, "success");
} catch (error) {
showToast(why(error), "error");
return steps.set("2");