- M02 왼쪽 [별칭 장부] · M02_MasterTemplete_Hopyo_Ledger.ts · 장부 타입 · css 지움
- 길 GET · PUT /api/m02/layers/{층}/hopyo-ledger 지움 · M02_Hopyo_Ledger 의 장부 화면 줄(rows · 승인 상태) 지움
- 일위대가 불러오기 확인(M02_Hopyo_Build) = 장부에 안 적음 · 가져온 식 저장(M02_Doc_Router) = 기계 장부에 안 적음
- B09 공사 내역 맞춤 기억(내역맞춤.json 읽기 · 쓰기 · 이 짝 잊기 길 · 단추) 걷음 — 제안은 짝 표만
- storage 옛 장부 파일(맞춤 · 내역맞춤 · 별칭반려)은 읽지 않게만 · 지우지 않음
- M02_Hopyo_Ledger 모듈은 남김 — 짝 검사 · 열쇠 도우미 · 요소 잇기의 옛 장부 읽기(M02_Machine_Find · M02_Hopyo_Units · M02_Hopyo_Sangeun — 815-1 몫)
- 시험: test_718_2_alias_gather · test_739_1_alias_central · test_m02_hopyo_build 지움(걷은 길 · 화면 지킴) · test_707_4 기억 층 · 잊기 시험을 「걷음」 시험으로 · test_m02_hopyo_ledger rows 줄 · test_m02_hopyo_screen · test_721 장부 화면 줄 고침
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
216 lines
7.7 KiB
TypeScript
216 lines
7.7 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Hopyo_Api.ts
|
|
* 일위대가 실무자료(PLAN 324 · 338) 서버 길 — `/api/m02/layers/{층}/hopyo…` · 층 = 화면 층(`Api_Fetch` 지금 층) ·
|
|
* 올리기 = FormData(숨은 file 입력) · 그 밖 = 공용 `call`(서버 까닭 글 그대로) ·
|
|
* 목록 · 올림 · 엑셀 보기 머리(809-1 줄 표지 줄상태) · 격자 · 지우기 · 붙여넣기 · 다시 잇기 —
|
|
* [수정] 보기 = `Hopyo_View`(809-1) · 판정 · 검산 · 옮기기 길(785)은 809-1 에서 지움(요구 703).
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
|
|
import { call, getActiveLayer, getActiveOwner, type Layer } from "./M02_MasterTemplete_Api_Fetch";
|
|
import type { LaborFixed } from "./M02_MasterTemplete_Labor_Calc";
|
|
|
|
/** 뒤 셈 중 다시 읽기 사이(밀리초) */
|
|
export const POLL_MS = 3000;
|
|
/** 사전조합 · 단가산출을 만들었음(document 알림) — 좌측 목록 · 엑셀 보기 칠 다시 */
|
|
export const HOPYO_BUILT = "m02-hopyo-built";
|
|
|
|
/** 층 · 주인(null = 자기 층) */
|
|
export interface HopyoScope {
|
|
layer: Layer;
|
|
owner: string | null;
|
|
}
|
|
|
|
export interface HopyoFile {
|
|
키: string;
|
|
파일: string;
|
|
올린때: string;
|
|
올린이: { id: number; 이름: string | null } | null;
|
|
시트: string;
|
|
호표수: number;
|
|
어긋남: number;
|
|
/** 올림 응답만 — 산근 시트를 못 읽은 사유(빈 파일 · DRM 암호 · 형식 오류 · 797-1) */
|
|
읽기불가?: string;
|
|
}
|
|
|
|
export type Money = Record<"계" | "노무비" | "재료비" | "경비", number | null>;
|
|
|
|
export interface HopyoBrief {
|
|
번: number;
|
|
이름: string;
|
|
규격: string;
|
|
단위: string;
|
|
금액: Money;
|
|
/** [1차 판정 제안 있음 수, 줄 수](785-7 `Judge.view`) */
|
|
맞춤: [number, number];
|
|
/** 줄마다 [차례, 1차 판정, 제안 있음] · 만든 SC 키(칠 · `SheetMap.BriefMarks`) */
|
|
줄상태?: [number, string, boolean][];
|
|
만든?: string | null;
|
|
/** 일위대가 시트 행 [시작, 끝](엑셀 1부터 · 329 · 옛 파일 없음) */
|
|
행?: [number, number];
|
|
}
|
|
|
|
/** 엑셀 보기 창 목록 한 줄 — 호표 · 단가산출 블록 행 범위 */
|
|
export interface HopyoSpan {
|
|
번: number;
|
|
이름: string;
|
|
행: [number, number];
|
|
}
|
|
|
|
/** 엑셀 보기 창 격자(329) — 시트 「일위대가」 · 「단가산출」 */
|
|
export interface HopyoGrid {
|
|
책: Workbook;
|
|
호표: HopyoSpan[];
|
|
단산: HopyoSpan[];
|
|
}
|
|
|
|
export interface HopyoView extends HopyoFile {
|
|
검산: { 어긋남: string[]; 총괄표: number };
|
|
호표: HopyoBrief[];
|
|
맞춤: [number, number];
|
|
/** 블록 풀이 뒤 셈(785-5 파생 저장본 · 끝 = 판정 칠 다 셈) */
|
|
셈?: { 끝: boolean; 까닭?: string };
|
|
}
|
|
|
|
/** [모두 고정으로 들여오기] 결과(705-3) */
|
|
export interface HopyoBuiltAll {
|
|
호표: number;
|
|
줄: number;
|
|
품셈: number;
|
|
고정: number;
|
|
호표참조: number;
|
|
빠짐: number;
|
|
이음: number;
|
|
이음후보: number;
|
|
같음: number;
|
|
그대로: number;
|
|
다름: {
|
|
번: number;
|
|
이름: string;
|
|
엑셀: number | null;
|
|
우리: number | null;
|
|
차이: number | null;
|
|
}[];
|
|
실패: { 번: number; 까닭: string }[];
|
|
/** 만든 · 그대로 둔 사전조합(705-2 · 다시 잇기) */
|
|
만듦?: { 번: number; 이름: string; 키: string }[];
|
|
}
|
|
|
|
const enc = encodeURIComponent;
|
|
/** 지금 화면 층(구조물 조합 층) */
|
|
export const pageScope = (): HopyoScope => ({ layer: getActiveLayer(), owner: getActiveOwner() });
|
|
export const at = (scope: HopyoScope, path: string): string => {
|
|
const who = scope.owner && scope.layer !== "system" ? `owner=${enc(scope.owner)}` : "";
|
|
const join = path.includes("?") ? "&" : "?";
|
|
return `/layers/${scope.layer}${path}${who ? join + who : ""}`;
|
|
};
|
|
|
|
export const listHopyo = async (scope: HopyoScope): Promise<HopyoFile[]> =>
|
|
(await call<{ 줄: HopyoFile[] }>(at(scope, "/hopyo"))).줄;
|
|
|
|
export const readHopyo = (scope: HopyoScope, key: string): Promise<HopyoView> =>
|
|
call(at(scope, `/hopyo/${enc(key)}`));
|
|
|
|
/** 격자 — 없으면 404 「격자 없음 — 다시 올리면 보임」(옛 파일) */
|
|
export const readHopyoGrid = (scope: HopyoScope, key: string): Promise<HopyoGrid> =>
|
|
call(at(scope, `/hopyo-grid/${enc(key)}`));
|
|
|
|
/** [끝내기] · 화면 떠남(743-1) — `keepalive` = 페이지를 닫아도 요청은 감 */
|
|
export const deleteHopyo = (scope: HopyoScope, key: string, keepalive = false): Promise<unknown> =>
|
|
call(at(scope, `/hopyo/${enc(key)}`), { method: "DELETE", keepalive });
|
|
|
|
/** 엑셀 올리기 — 서버 까닭 글 그대로(시트 없음 · 20 MB · 엉터리) */
|
|
export async function uploadHopyo(scope: HopyoScope, file: File): Promise<HopyoFile> {
|
|
const form = new FormData();
|
|
form.append("file", file);
|
|
const response = await fetch(`${API_BASE_URL}/m02${at(scope, "/hopyo")}`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
body: form,
|
|
});
|
|
const body = (await response.json().catch(() => ({}))) as { detail?: unknown } & HopyoFile;
|
|
if (!response.ok)
|
|
throw new Error(typeof body.detail === "string" ? body.detail : `HTTP ${response.status}`);
|
|
return body;
|
|
}
|
|
|
|
/** 엑셀 붙여넣기 열 짝(705-2) — 칸 차례(0부터) · 노 · 재 · 경 = 단가 칸 · 금액 = 합계 금액 칸 */
|
|
export type PasteCols = Partial<
|
|
Record<"품명" | "규격" | "수량" | "단위" | "금액" | "노" | "재" | "경", number>
|
|
>;
|
|
|
|
/** 붙여넣기 답 — 물음(열 짝 짐작 + 보기) · 줄(고정 줄 · 문서에 넣음) · 만듦(호표마다 사전조합) */
|
|
export type PasteResult =
|
|
| { 갈래: "물음"; 열: PasteCols; 짐작: boolean; 보기: string[][] }
|
|
| {
|
|
갈래: "줄";
|
|
열: PasteCols;
|
|
줄: { 고정: LaborFixed; 별칭: string | null; 메모: string; 수량: { 손: number | null } }[];
|
|
엑셀: number;
|
|
이음: number;
|
|
이음후보: number;
|
|
}
|
|
| ({ 갈래: "만듦"; 열: PasteCols; 키: string } & HopyoBuiltAll);
|
|
|
|
/** [Ctrl+V](705-2) — 붙인 글 그대로(숫자는 서버가 글에서) */
|
|
export const pasteHopyo = (
|
|
scope: HopyoScope,
|
|
글: string,
|
|
열: PasteCols | null,
|
|
opt: { 줄만?: boolean; 있음?: string[] } = {},
|
|
): Promise<PasteResult> =>
|
|
call(at(scope, "/hopyo-paste"), {
|
|
method: "POST",
|
|
body: JSON.stringify({ 글, ...(열 ? { 열 } : {}), ...opt }),
|
|
});
|
|
|
|
/** 다시 잇기 표 한 줄(705-2) — 붙인 단가 · 이은 단가(지금) · 후보 */
|
|
export interface RelinkRow {
|
|
키: string | null;
|
|
줄: string;
|
|
이름: string;
|
|
규격: string;
|
|
단위: string;
|
|
수량: number | null;
|
|
붙인: Record<"노무비" | "재료비" | "경비", number>;
|
|
이음: Record<"노임" | "자재", string | null>;
|
|
지금: Partial<Record<"노임" | "자재", number | null>>;
|
|
후보: {
|
|
비목: "노임" | "자재";
|
|
키: string;
|
|
이름: string;
|
|
규격: string;
|
|
단위: string;
|
|
단가: number | null;
|
|
}[];
|
|
}
|
|
|
|
/** 바꿈 한 줄 — 값 null = 이음 풂 */
|
|
export interface RelinkChange {
|
|
키: string;
|
|
줄: string;
|
|
노임?: string | null;
|
|
자재?: string | null;
|
|
}
|
|
|
|
export const relinkRows = async (
|
|
scope: HopyoScope,
|
|
body: { 키?: string[]; 문서?: unknown },
|
|
): Promise<RelinkRow[]> =>
|
|
(
|
|
await call<{ 줄: RelinkRow[] }>(at(scope, "/hopyo-relink/rows"), {
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
})
|
|
).줄;
|
|
|
|
export const relinkApply = (
|
|
scope: HopyoScope,
|
|
바꿈: RelinkChange[],
|
|
): Promise<{
|
|
바뀜: number;
|
|
문서: { 키: string; 이름: string; 전: number | null; 후: number | null }[];
|
|
되돌림: RelinkChange[];
|
|
}> => call(at(scope, "/hopyo-relink"), { method: "POST", body: JSON.stringify({ 바꿈 }) });
|