feat(B09): 유가 지역 단가를 물림 — 시도를 고르면 그 값으로 기계 연료비가 다시 섬
품셈 8-1-7 5호 「유류가격은 해당지역의 가격으로 한다」. 칸은 있었으나 시도별 값이 없어 잠겨 있었음. 오피넷 avgSidoPrice.do 스냅샷을 받아 데이터셋으로 세우고 물림. - 안 고르면 전국평균 그대로 — 현장 소재지를 임의로 찍지 않음. - 판에 없는 지역은 조용히 전국평균으로 눕지 않고 사유를 남김. - 「지역 공시가」를 고를 수 있는지는 코드가 아니라 판이 정함. - ⚠ 원천의 시도 가름이 행정구역과 다름(20=전남광주 한 줄, 07·16 없음) — 원문 그대로 둠. - 수집 스크립트에 시도별 호출을 더함(전국평균과 두 벌로 보존). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -254,6 +254,11 @@ export interface PriceSourcesDto {
|
||||
effective_date: string;
|
||||
dataset_id: string;
|
||||
scopes: FuelScope[];
|
||||
/** 고를 수 있는 시도 — 판에 있는 것만. 값을 함께 실어 고르기 전에 견줄 수 있다. */
|
||||
regions: Array<{ code: string; name: string; diesel_krw_per_l: string | null }>;
|
||||
region: string;
|
||||
region_name: string;
|
||||
region_missing?: string;
|
||||
note: string;
|
||||
};
|
||||
};
|
||||
@@ -346,7 +351,12 @@ function comparisonTable(slotNames: string[], rows: MaterialComparisonRow[]): HT
|
||||
}
|
||||
|
||||
/** 환율및기초자료 — 실무 시트 세 구획(환율·인건비·단가 및 재료비)을 차례대로. */
|
||||
function baseReferenceSections(body: HTMLElement, data: PriceSourcesDto["base_reference"]): void {
|
||||
function baseReferenceSections(
|
||||
body: HTMLElement,
|
||||
data: PriceSourcesDto["base_reference"],
|
||||
projectId: string,
|
||||
reload: () => void,
|
||||
): void {
|
||||
body.append(head("환율및기초자료 — ① 환율"));
|
||||
body.append(note(data.exchange.note));
|
||||
|
||||
@@ -397,32 +407,61 @@ function baseReferenceSections(body: HTMLElement, data: PriceSourcesDto["base_re
|
||||
|
||||
// ⚠ 확정 ⑮ — 전국/지역을 고르는 칸. 자료가 없는 것은 **고를 수 없게** 두고
|
||||
// 까닭을 곧바로 밝힌다. 고르게만 해 두고 값이 없으면 조용히 틀린 값이 선다.
|
||||
// ⚠ 시도가 들어온 뒤로는 **한 칸에서 전국과 시도를 함께** 고른다 — 범위 칸과 시도 칸을
|
||||
// 따로 두면 「지역인데 시도를 안 고른 상태」가 생겨 무슨 값으로 섰는지 흐려진다.
|
||||
const picker = document.createElement("div");
|
||||
picker.className = "b09-hint";
|
||||
picker.style.display = "flex";
|
||||
picker.style.alignItems = "center";
|
||||
picker.style.gap = "8px";
|
||||
picker.style.flexWrap = "wrap";
|
||||
const label = document.createElement("span");
|
||||
label.textContent = "유가 적용 범위";
|
||||
const select = document.createElement("select");
|
||||
for (const scope of fuel.scopes) {
|
||||
const national = document.createElement("option");
|
||||
national.value = "";
|
||||
national.textContent = "전국 공시가";
|
||||
national.selected = !fuel.region;
|
||||
select.append(national);
|
||||
for (const region of fuel.regions) {
|
||||
const option = document.createElement("option");
|
||||
option.value = scope.key;
|
||||
option.textContent = scope.available ? scope.label : `${scope.label} (자료 없음)`;
|
||||
option.disabled = !scope.available;
|
||||
option.selected = scope.key === fuel.scope;
|
||||
option.value = region.code;
|
||||
option.textContent = `${region.name} ${region.diesel_krw_per_l ?? ""}원/L`;
|
||||
option.selected = region.code === fuel.region;
|
||||
select.append(option);
|
||||
}
|
||||
select.disabled = fuel.regions.length === 0;
|
||||
select.addEventListener("change", () => {
|
||||
void saveFactorChoices(projectId, { fuel_region: select.value })
|
||||
.then(reload)
|
||||
.catch((error: Error) => body.append(note(`⚠ ${error.message}`)));
|
||||
});
|
||||
picker.append(label, select);
|
||||
body.append(picker);
|
||||
for (const scope of fuel.scopes) {
|
||||
if (!scope.available && scope.why) body.append(note(`⚠ ${scope.label}: ${scope.why}`));
|
||||
if (fuel.regions.length === 0) {
|
||||
for (const scope of fuel.scopes) {
|
||||
if (!scope.available && scope.why) body.append(note(`⚠ ${scope.label}: ${scope.why}`));
|
||||
}
|
||||
} else {
|
||||
body.append(
|
||||
note(
|
||||
fuel.region
|
||||
? `${fuel.region_name} 공시가로 서 있습니다 — 기계 연료비가 그 값으로 다시 섭니다.`
|
||||
: "전국 공시가로 서 있습니다 — 현장 시도를 고르면 그 지역 값으로 바뀝니다.",
|
||||
),
|
||||
);
|
||||
}
|
||||
if (fuel.region_missing) body.append(note(`⚠ ${fuel.region_missing}`));
|
||||
body.append(note(fuel.note));
|
||||
}
|
||||
|
||||
/** 기초자료 탭 아래쪽 — A9·A10 두 장. */
|
||||
export function drawPriceSourcesSections(body: HTMLElement, data: PriceSourcesDto): void {
|
||||
export function drawPriceSourcesSections(
|
||||
body: HTMLElement,
|
||||
data: PriceSourcesDto,
|
||||
projectId: string,
|
||||
reload: () => void,
|
||||
): void {
|
||||
const comparison = data.material_comparison;
|
||||
body.append(head(`자재단가대비표 (${comparison.rows.length})`));
|
||||
if (comparison.rows.length <= 1) {
|
||||
@@ -439,7 +478,7 @@ export function drawPriceSourcesSections(body: HTMLElement, data: PriceSourcesDt
|
||||
}
|
||||
for (const text of comparison.notes) body.append(note(text));
|
||||
|
||||
baseReferenceSections(body, data.base_reference);
|
||||
baseReferenceSections(body, data.base_reference, projectId, reload);
|
||||
}
|
||||
|
||||
/** 두 표를 아직 못 받아왔을 때 — 화면을 비우지 않는다. */
|
||||
@@ -520,6 +559,7 @@ export async function saveFactorChoices(
|
||||
range_factor_choices?: Record<string, string>;
|
||||
machine_choices?: Record<string, string>;
|
||||
misc_material_percent?: string;
|
||||
fuel_region?: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
const response = await fetch(
|
||||
|
||||
Reference in New Issue
Block a user