- B09 데이터 점검 → 내역 점검 · 공기 옵션 → 공사기간 조건 · 박스보기/호표보기 → 카드 보기/표 보기 · 하이퍼링크/팝업 → 탭 이동/곁 창 · 굳힌 단가 → 이 공사 단가 책 · 산식/요율 출처 → 계산식/요율 근거 · 빈 줄 → 금액 빈 줄 · 단가연결 칸 램프 → 단가연결 칸 ● · 새 산근 → 새 단가산출(산근) · 연결 없음 → 일위대가 없음 - 단가연결 창 종류 일위대가 사전조합 → 일위대가(자료집) · 일위대가 호표 → 이 공사 일위대가 · 단가산출 호표 → 이 공사 단가산출 · 대가N/산근 N호표 → 일위대가 N호/단가산출 N호 · 구조물 조합 줄 번호(SS…) 숨김 · 넣을 값 창 제목 번호 → 알림 글 끝 - B08 조합 사본 · 조합 연결 · 노랑 램프 → 구조물 조합 · 구조물 조합 고르기 · 노랑 ●(locale_b4 B08_Quantity_* 글만 · 한시 브레인 승인) - ☰ 서랍 · 진행 판 끝 「내 자료집(일위대가 · 구조물 조합)」 = M02 일위대가조합 개인 층(떠남 물음 뒤) - 낡은 시험 글 고침(새 낱말): 143-1 · 157-2 · 182 · 203-1 · 204-2 · 304 · 311 · 701-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
110 lines
4.3 KiB
TypeScript
110 lines
4.3 KiB
TypeScript
/* =============================================================================
|
|
* B09_Estimation_UI_Live.ts
|
|
* B09 조작 중 서버 셈(PLAN 702-3) — 새 산근(단가연결 · 로직 입력값)은 서버 M01 엔진 몫 →
|
|
* 바뀌면 그 자리에서 뒤로 POST …/estimation/calc {user}(저장 안 함 · GET 꼴 답) ·
|
|
* 차례 번호 · 옛 요청 끊기(`design_pipeline` 꼴) · 오는 동안 그 줄 금액 칸만 「셈 중」 · 화면 잠금 없음
|
|
*
|
|
* 기준 = payload.user(서버가 그 셈에 쓴 사용자 값) — 로직 연결 지문이 다른 줄 = 기다림 ·
|
|
* 기다리는 줄은 옛 산근 값을 안 씀(sg_hand 에서 뺌) · 못 받으면 알림 뒤 빈칸(같은 지문 다시 안 보냄)
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
import { showToast } from "@ui/ui_template_elements";
|
|
import type { Row } from "./B09_Estimation_Calc_Base";
|
|
|
|
export const WAITING = "셈 중";
|
|
|
|
/** 줄 키 → 로직 연결 지문(코드 · 입력값 · 켜쌓기율 · 고름) */
|
|
function logicSig(user: Row | null | undefined): Map<string, string> {
|
|
const out = new Map<string, string>();
|
|
for (const r of user?.bill?.rows || []) {
|
|
const ref = r.ref;
|
|
if (ref?.tab === "logic")
|
|
out.set(
|
|
r.key,
|
|
JSON.stringify([ref.code, ref.given ?? {}, ref.rate ?? null, ref.picks ?? null]),
|
|
);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** 서버 셈을 기다리는 줄 — 로직 연결이 서버 기준(payload.user)과 다름 */
|
|
export function waitingKeys(payload: Row, user: Row): Set<string> {
|
|
const known = logicSig(payload.user);
|
|
const out = new Set<string>();
|
|
for (const [key, sig] of logicSig(user)) if (known.get(key) !== sig) out.add(key);
|
|
return out;
|
|
}
|
|
|
|
/** 셈 입력 — 기다리는 줄은 옛 산근 단가를 안 씀 */
|
|
export function withoutWaiting(payload: Row, wait: Set<string>): Row {
|
|
if (!wait.size) return payload;
|
|
const hand = { ...(payload.sheets?.sg_hand || {}) };
|
|
for (const key of wait) delete hand[key];
|
|
return { ...payload, sheets: { ...payload.sheets, sg_hand: hand } };
|
|
}
|
|
|
|
/** 기다리는 줄 금액 칸 = 「셈 중」(셈이 끝난 뒤 · 계에는 안 듦) */
|
|
export function markWaiting(sheets: Row, wait: Set<string>): void {
|
|
for (const r of sheets.bill?.rows || []) if (wait.has(r.key) && r.sum == null) r.sum = WAITING;
|
|
}
|
|
|
|
/** settle(next, sent) — next = 서버 답(못 받음 = null) · sent = 보낸 사용자 값 */
|
|
export function createLiveCalc(
|
|
projectId: string,
|
|
settle: (next: Row | null, sent: Row) => void,
|
|
): { busy: () => boolean; kick: (wait: Set<string>, user: Row) => void } {
|
|
let seq = 0;
|
|
let ctrl: AbortController | null = null;
|
|
let sentSig = "";
|
|
let failedSig = "";
|
|
return {
|
|
busy: () => ctrl !== null,
|
|
/** 기다리는 줄 있으면 서버 셈(같은 것이 가는 중 · 못 받은 것이면 그대로) · 없으면 가던 것 끊음 */
|
|
kick(wait, user) {
|
|
const sig = JSON.stringify([...logicSig(user)]);
|
|
if (wait.size && (sig === failedSig || (ctrl && sig === sentSig))) return;
|
|
ctrl?.abort();
|
|
ctrl = null;
|
|
seq += 1;
|
|
if (!wait.size) return;
|
|
const mine = seq;
|
|
const me = new AbortController();
|
|
ctrl = me;
|
|
sentSig = sig;
|
|
fetch(`${API_BASE_URL}/projects/${projectId}/estimation/calc`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ user }),
|
|
signal: me.signal,
|
|
})
|
|
.then(async (response) => {
|
|
const body = (await response.json().catch(() => null)) as Row | null;
|
|
if (!response.ok || !body) {
|
|
const detail = body?.detail;
|
|
throw new Error(typeof detail === "string" ? detail : `HTTP ${response.status}`);
|
|
}
|
|
return body;
|
|
})
|
|
.then(
|
|
(next) => {
|
|
if (mine !== seq) return;
|
|
ctrl = null;
|
|
settle(next, user);
|
|
},
|
|
(error: unknown) => {
|
|
if (mine !== seq || me.signal.aborted) return;
|
|
ctrl = null;
|
|
failedSig = sig;
|
|
showToast(
|
|
`새 단가산출 단가를 못 받음 — [저장] 때 다시 셈 · ${String(error)}`,
|
|
"warning",
|
|
);
|
|
settle(null, user);
|
|
},
|
|
);
|
|
},
|
|
};
|
|
}
|