feat(B09): 기계 수송비를 세움 — 기계경비의 셋째 몫이 통째로 빠져 있었음

기계경비 = 기계손료 + 운전경비 + 수송비인데 수송비가 없었음. 수송비를 내는 공종
FP-10-04 중기운반은 표가 미판정이라 단가가 아예 안 섰음.

- 산림품셈 10-4 사이클 그대로 — 트레일러(20TON) t1=20·t3=20·t4=0.42 /
  트럭(10.5TON) t1=10·t3=10·t4=5 · ㎝=t1+t2+t3+t4 · N=60×0.9/㎝, 단위는 회당.
- 거리·도로 구분은 설계 입력 — 비면 줄이 안 서고 사유만 남음(임의 거리 금지).
- 원문이 「-」로 둔 칸(트레일러·고속4차선)은 지어내지 않고 사유로 냄.
- ⚠ 우리가 정한 둘을 화면 근거에 적음 — 속도를 8-1-6의 2 나 이동속도표에서 가져온 것,
  운반시간을 왕복으로 본 것.
- 곁다리: 운전경비 표가 페이지에서 잘려 앞자리를 못 이어받아 39 기종이 통째로
  버려지고 있었음(그 안에 수송 차량 2702 가 있었음). 이어받되 카탈로그에 있는
  코드일 때만 받게 함 — 운전경비 줄 92 → 158.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-09 22:23:06 +09:00
co-authored by Claude Opus 5
parent 08c0ec3134
commit fdc30ecf17
6 changed files with 473 additions and 6 deletions
@@ -536,11 +536,22 @@ export interface MiscMaterialRow {
base_note: string;
}
/** 기계 수송비 칸 — 거리·도로 구분이 있어야 줄이 선다(산림품셈 10-4). */
export interface TransportRow {
distance_km: string;
road: string;
roads: Array<{ key: string; label: string }>;
variants: Array<{ key: string; label: string; unit_price_krw: string }>;
basis: string[];
notes: string[];
}
export interface FactorChoicesDto {
status: string;
ranges: RangeFactorRow[];
machines: MachineChoiceRow[];
misc_material?: MiscMaterialRow;
transport?: TransportRow;
notes: string[];
}
@@ -560,6 +571,8 @@ export async function saveFactorChoices(
machine_choices?: Record<string, string>;
misc_material_percent?: string;
fuel_region?: string;
transport_distance_km?: string;
transport_road?: string;
},
): Promise<void> {
const response = await fetch(
@@ -726,5 +739,48 @@ export function drawFactorChoices(
for (const line of misc.basis) body.append(note(line));
}
const transport = data.transport;
if (transport) {
body.append(
percentBox(
"기계 수송 거리 (인근 시·군·구청 → 현장, 편도 ㎞)",
transport.distance_km,
"비움",
(text) => {
void saveFactorChoices(projectId, { transport_distance_km: text })
.then(reload)
.catch((error: Error) => body.append(note(`${error.message}`)));
},
),
);
const roadOptions: FactorOption[] = [
{ key: "", label: "안 고름" },
...transport.roads.map((road) => ({ key: road.key, label: road.label })),
];
body.append(
picker("수송 도로 구분", roadOptions, transport.road, (key) => {
void saveFactorChoices(projectId, { transport_road: key })
.then(reload)
.catch((error: Error) => body.append(note(`${error.message}`)));
}),
);
for (const variant of transport.variants) {
body.append(
note(
variant.unit_price_krw
? `${variant.label} — 회당 ${variant.unit_price_krw}`
: `${variant.label} — 아직 안 섬`,
),
);
}
for (const line of transport.notes) body.append(note(`${line}`));
for (const line of transport.basis) body.append(note(line));
body.append(
note(
"⚠ 단가는 「회당」입니다 — 몇 대를 몇 번 나르는지(회수)는 설계 입력이라 여기서 안 정합니다.",
),
);
}
for (const line of data.notes) body.append(note(line));
}