feat(b09): 원가계산 화면 새 틀 — 설계내역서·일위대가·단가산출근거 탭(실무 서식 · 내역 → 호표 → 산근 들어가기)
- 틀 B09_Estimation_UI_Shell: 탭 줄과 등록만 · 탭마다 파일 하나(계약 _Shell_Types) - 설계내역서: 실무 열(합계/노무/재료/경비 단가·금액) · 머리글 접기·레벨 고르개 · 줄 누르면 제 N 호표 · 단산 N 단추 · 미확정 빨간 테두리 - 일위대가·단가산출근거: 목록표(내역에 처음 쓰인 차례) + 본표 · 줄 누르면 하위 호표·산근·중기로 · 자취 눌러 되돌아감 · Q 식 글자 그대로 - 옛 탭 여섯(원가계산서·중기·관급사급·기초자료·설계서 구성·산출기초)은 옛 코드 그대로 이어 붙임 — 새 탭 파일이 서면 등록 한 줄씩 바꿈 - 본표 합계 줄 = 호표 성분 소계 원 미만 절사 값 · 비율 줄 금액도 0.1원 절사 표시 - 사전 ui_template_locale_b3 새 벌(b2 700줄 넘음) - 검증: ORCA 검증 프로젝트 — 본체 122,848,989 · 지장목제거 865·15,460,837 · 제 9 호표 합계 1,708 = 산근 8호표 합계 1,708 · 옛 탭 여섯 다 뜸 · 접기 53→51·레벨1 11줄 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Store.ts
|
||||
* B09 내역서·일위대가·단가산출근거 탭이 **한 벌로** 쓰는 서버 자료 (PLAN 12장)
|
||||
*
|
||||
* - 설계내역서 응답 한 벌에 호표 목록(`unit_price_sheet`)·단산 목록(`price_basis`)이 함께 옴 —
|
||||
* 세 탭이 같은 응답을 봄(번호가 탭끼리 갈리지 않게). 번호는 서버가 낼 때마다 매김(저장 안 함).
|
||||
* - 캐시는 메모리 한 벌(프로젝트별) — [다시 불러오기] 가 비움. 값을 저장하지 않음(보기 전용 1차).
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export interface BillDto {
|
||||
rows: BillRowDto[];
|
||||
excluded: BillRowDto[];
|
||||
summary: {
|
||||
body_total_krw: string;
|
||||
detail_rows: number;
|
||||
missing: MissingDto[];
|
||||
unconfirmed_count: number;
|
||||
notes: string[];
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
/** 설계내역서 한 벌 — 세 탭이 같은 응답을 봄. `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>;
|
||||
}
|
||||
|
||||
/** 호표 본표 — 일위대가(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)}`,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user