Files
Aislo/B09_Estimation/B09_Estimation_UI_Store.ts
T
eomsangdonandClaude Opus 5 ef48e6edd3 refactor(b09): 옛 탭 옮기기 ⑤ 기초자료 — 산출 조건(UI_Factors) · 목록표 셋 · 자재단가대비표 · 환율및기초자료를 새 틀 탭 파일로
- 저장하면 이 탭 자료와 설계내역서 한 벌(UI_Store forgetBill)을 함께 새로 받음 — 값이 다시 섬
- 화면 확인: 옮기기 전후 같음 — 표 6 · 줄 737 · 고르개 32 개 고른 값 전부 같음 · 숫자 칸 2 · 글 35,074 자
- 계산 입력 확인: 기계 작업효율 E 평균 0.50 → 상한 0.55 를 화면에서 고르면 「사용자가 고른 값」 · 제 2 호표 2,511 → 2,284 ·
  되돌린 뒤(기본값) 2,511 그대로 — 검증 프로젝트 설정 원래대로 복구

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-14 02:47:03 +09:00

237 lines
6.4 KiB
TypeScript

/* =============================================================================
* B09_Estimation_UI_Store.ts
* B09 내역서·일위대가·단가산출근거 탭이 **한 벌로** 쓰는 서버 자료 (PLAN 12장)
*
* - 설계내역서 응답 한 벌에 호표 목록(`unit_price_sheet`)·단산 목록(`price_basis`)이 함께 옴 —
* 세 탭이 같은 응답을 봄(번호가 탭끼리 갈리지 않게). 번호는 서버가 낼 때마다 매김(저장 안 함).
* - 캐시는 메모리 한 벌(프로젝트별) — [다시 불러오기] 가 비움. 값을 저장하지 않음(보기 전용 1차).
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
import type { ProvenancePayload } from "@ui/ui_template_provenance";
export interface BillNoteDto {
column: string;
text: string;
}
export interface BillRowDto {
item_no: string;
level: number;
code: string | null;
name: string;
spec: string;
unit: string;
quantity: string | null;
quantity_shown: string | null;
quantity_digits: number | null;
unit_price_krw: string | null;
amount_krw: string | null;
material_krw: string;
labor_krw: string;
expense_krw: string;
unit_material_krw: string | null;
unit_labor_krw: string | null;
unit_expense_krw: string | null;
price_code: string;
unconfirmed: number;
price_basis_label: string;
is_group: boolean;
in_bill: boolean;
note: string;
notes: BillNoteDto[];
}
export interface SheetEntryDto {
number: number;
label: string;
code: string;
name: string;
spec: string;
unit: string;
material_krw: string;
labor_krw: string;
expense_krw: string;
total_krw?: string;
unit_price_krw?: string;
source?: string;
unconfirmed?: number;
ref_code?: string;
unit_price_codes?: string[];
}
export interface MissingDto {
name: string;
unit?: string;
quantity?: string;
reason: string;
code?: string;
blocked_kind?: string;
}
/** 집계표·목록표 한 줄 — 서버 `resource_summary`·`bill_lists` 그대로. */
export interface ResourceRowDto {
number: number;
code: string;
name: string;
spec: string;
unit: string;
quantity?: string;
unit_price_krw: string | null;
amount_krw?: string | null;
labor_krw?: string;
material_krw?: string;
expense_krw?: string;
unit_labor_krw?: string;
unit_material_krw?: string;
unit_expense_krw?: string;
note: string;
}
export type ResourceGroup = "labor" | "material" | "expense" | "machine" | "lumpsum";
export interface MaterialSheetRowDto {
name: string;
spec: string;
unit: string;
total_amount: string;
unit_price_krw: string | null;
amount_krw: string | null;
note: string;
}
export interface MaterialSheetDto {
contractor: MaterialSheetRowDto[];
owner: MaterialSheetRowDto[];
unknown: MaterialSheetRowDto[];
contractor_total_krw: string;
owner_total_krw: string;
notes: string[];
}
export interface BillDto {
/** 근거 사전 — 개발환경에서만 옴. */
provenance?: ProvenancePayload;
resources: {
groups: Record<ResourceGroup, ResourceRowDto[]>;
missing: string[];
note: string;
};
lists: Record<ResourceGroup, ResourceRowDto[]>;
rows: BillRowDto[];
excluded: BillRowDto[];
summary: {
body_total_krw: string;
detail_rows: number;
missing: MissingDto[];
unconfirmed_count: number;
notes: string[];
material_sheet: MaterialSheetDto | null;
};
price_basis: { entries: SheetEntryDto[] };
unit_price_sheet: { entries: SheetEntryDto[] };
}
export interface DetailRowDto {
ref_code?: string;
code?: string;
name: string;
spec: string;
unit: string;
kind?: string;
source_label?: string;
source?: string;
drillable: boolean;
quantity: string;
unit_material?: string;
unit_labor?: string;
unit_expense?: string;
unit_total?: string;
material: string;
labor: string;
expense: string;
total: string;
note: string;
}
export interface DetailDto {
code: string;
name: string;
spec: string;
unit: string;
kind: string;
material: string;
labor: string;
expense: string;
total: string;
rows: DetailRowDto[];
unattached_note: string;
known_gap_note: string;
}
const bills = new Map<string, Promise<BillDto>>();
async function getJson<T>(path: string): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, { credentials: "include" });
const body = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(String((body as { message?: string }).message || response.status));
}
return body as T;
}
/** 산출 조건을 저장한 뒤 — 단가가 다시 서므로 다음에 고르는 탭이 새로 받게 비움. */
export function forgetBill(projectId: string): void {
bills.delete(projectId);
}
/** 설계내역서 한 벌 — 세 탭이 같은 응답을 봄. `force` 면 다시 받음. */
export function loadBill(projectId: string, force = false): Promise<BillDto> {
if (force || !bills.has(projectId)) {
const request = getJson<BillDto>(`/projects/${encodeURIComponent(projectId)}/estimation/bill`);
request.catch(() => bills.delete(projectId));
bills.set(projectId, request);
}
return bills.get(projectId) as Promise<BillDto>;
}
export interface PriceSlotDto {
name: string;
price_krw: string | null;
source_note: string;
adopted: boolean;
}
export interface PriceCompareDto {
material_comparison: {
slot_names: string[];
rows: Array<{
code: string;
name: string;
spec: string;
unit: string;
slots: PriceSlotDto[];
min_slot: number | null;
adopted_slot: number;
adopted_price_krw: string | null;
note: string;
}>;
notes: string[];
};
}
/** 자재단가대비표 — 슬롯 여섯 · 채택 · 최소단가(서버 `material_price_comparison`). */
export function loadPriceCompare(projectId: string): Promise<PriceCompareDto> {
return getJson<PriceCompareDto>(
`/projects/${encodeURIComponent(projectId)}/estimation/price-sources`,
);
}
/** 호표 본표 — 일위대가(B)·시간당 중기(X)는 `unit-prices`, 단가산출(D)은 `price-basis`. */
export function loadDetail(projectId: string, code: string): Promise<DetailDto> {
const kind = code.startsWith("D-") ? "price-basis" : "unit-prices";
return getJson<DetailDto>(
`/projects/${encodeURIComponent(projectId)}/estimation/${kind}/${encodeURIComponent(code)}`,
);
}