- 저장 노선 측점을 갈아끼운 뒤 묶음이 다 돈 다음 cross 한 번 더(그 차례 cross 는 옛 상세 · 옛 화면 선을 봄) - 다시 셈 창구 앞에서 초안 형상을 다시 얹음(B06 폼 되보내기가 관 제원에 옵션 값을 그대로 써 최소 높이 · 집수정 쪽 칸이 서버와 갈림) - 화면(검증_sub4) B05 만 연 절토 · 성토 합 = B06 연 뒤 442.3726 · 824.768 · 관 옵션 2 → 2.1 뒤도 443.2174 같음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
211 lines
10 KiB
TypeScript
211 lines
10 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_Section_Adjusts.ts
|
|
* 저장 안 한 조정창 값(세션 · 측점 누가거리 열쇠)을 상세 설계 칸에 얹음 — 상세 가게가 읽을 때 한 번(387-4).
|
|
*
|
|
* 조정창(`_UI_Page_Station_Controls` · `_Ford_Controls` · `_Span_Control`)이 고칠 때 캐시에 얹는 것과 같은
|
|
* 칸 · 같은 꼴. 예전에는 고칠 때만 얹어 새로고침 · B05 만 연 페이지는 세션 값을 몰랐음 — 카드 그리기가 면적을
|
|
* 고쳐 들어 B06 을 연 뒤에만 면적이 바뀜(B05 만 연 면적 ≠ B06). 조정창은 세션 → 설계 순으로 읽으니 겹쳐도 같음.
|
|
*
|
|
* 관 옵션(폼 칸 · 관 초안 ?? 저장 관)의 조정값 몫도 여기서(387-4b · `layPipeOptions`) — 관 벽 높이 · 형태(h · m) ·
|
|
* 흙막이 n 단(h · m · 구간). 예전에는 B06 보기가 열릴 때 폼이 되보내야만 얹혀 B05 만 연 면적이 달랐음.
|
|
* 형상(관 세트 · 흙막이 세트 · 도수로 · 세월교 · BOX 제원)은 형상 얹기(`attachShapes`)가 이미 함.
|
|
* 같은 규칙 = `B06_Section_UI_Page_Pipe_Options`(폼을 고친 그 자리 즉시 반영 몫).
|
|
* ========================================================================== */
|
|
|
|
import { readState } from "../A00_Common/b_page_state";
|
|
import type { CrossDesign, SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
|
import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types";
|
|
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
|
|
import { fordAdjustOf } from "./B06_Section_UI_Cross_Ford_Geom";
|
|
import { DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box_Geom";
|
|
import { PIPE_MATCH_M } from "../A00_Common/design/design_selection";
|
|
import { applySpanPatch, WALL_MIN_PURE_HEIGHT_M } from "./logic/B06_Section_UI_Cross_Culvert_Const";
|
|
import { tierSpanOf } from "./B06_Section_UI_Cross_Culvert_Wire";
|
|
import { computeStoredLayouts } from "./B06_Section_Structure_Layouts";
|
|
|
|
type Cells = Record<string, unknown>;
|
|
type Obj = Record<string, unknown>;
|
|
const INLET = ["auto", "revet", "I", "L", "U"];
|
|
const isObj = (value: unknown): value is Obj => !!value && typeof value === "object";
|
|
const count = (value: unknown): number =>
|
|
Number.isInteger(value) && (value as number) > 0 ? (value as number) : 0;
|
|
|
|
/** 조정창 읽기와 같은 꼴 — 옛 숫자 = x 이동량 · d 0 = 기본 자리(null). */
|
|
function wallAdjust(value: unknown): WallAdjust | null {
|
|
if (typeof value === "number" && Number.isFinite(value)) return { ...ZERO_ADJUST, x: value };
|
|
if (!isObj(value)) return null;
|
|
const partial = value as Partial<WallAdjust>;
|
|
return { ...ZERO_ADJUST, ...partial, d: partial.d ? partial.d : null };
|
|
}
|
|
|
|
export function withDraftAdjusts(
|
|
detail: SectionDetailResponse,
|
|
projectId: string,
|
|
routeId: number,
|
|
): void {
|
|
const read = (name: Parameters<typeof readState>[0]): Cells =>
|
|
readState<Cells>(name, projectId, routeId) ?? {};
|
|
const revet = read("revetx");
|
|
const basin = read("basinadjust");
|
|
const inlet = read("inletstruct");
|
|
const extra = read("extrawall");
|
|
const spans = read("extraspan");
|
|
const ford = read("fordadjust");
|
|
const box = read("boxadjust");
|
|
const link = read("revetlink") as { detached?: Cells; grade?: Cells };
|
|
for (const section of detail.cross_sections) {
|
|
const design = section.design as (CrossDesign & Obj) | null | undefined;
|
|
if (!design) continue;
|
|
const at = section.chainage_m.toFixed(2);
|
|
const own = (cells: Cells): Array<[string, unknown]> =>
|
|
Object.entries(cells)
|
|
.filter(([key]) => key.startsWith(`${at}:`))
|
|
.map(([key, value]) => [key.slice(at.length + 1), value]);
|
|
for (const [role, value] of own(revet)) {
|
|
const adjust = wallAdjust(value);
|
|
if (adjust) design.revet_adjust = { ...(design.revet_adjust ?? {}), [role]: adjust };
|
|
}
|
|
if (typeof inlet[at] === "string" && INLET.includes(inlet[at] as string))
|
|
design.inlet_structure = inlet[at] as CrossDesign["inlet_structure"];
|
|
if (isObj(basin[at])) design.basin_adjust = { ...DEFAULT_BASIN_ADJUST, ...basin[at] };
|
|
if (at in extra || `b${at}` in extra)
|
|
design.extra_wall_counts = { outlet: count(extra[at]), basin: count(extra[`b${at}`]) };
|
|
for (const [wall, value] of own(spans)) {
|
|
const span = value as { lengthM: number; beforeM: number; afterM: number } | null;
|
|
if (!span || !Number.isFinite(span.beforeM) || !Number.isFinite(span.afterM)) continue;
|
|
design.extra_spans = {
|
|
...(design.extra_spans ?? {}),
|
|
[wall]: { length_m: span.lengthM, before_m: span.beforeM, after_m: span.afterM },
|
|
};
|
|
}
|
|
if (isObj(link.detached) && at in link.detached)
|
|
design.revet_link_detached = !!link.detached[at];
|
|
if (isObj(link.grade) && at in link.grade) design.revet_follow_grade = !!link.grade[at];
|
|
if (isObj(ford[at])) design.ford_adjust = fordAdjustOf(ford[at]);
|
|
const sides = box[at] as { left?: Obj; right?: Obj } | undefined;
|
|
if (isObj(sides))
|
|
design.box_adjust = {
|
|
left: { ...DEFAULT_BOX_SIDE_ADJUST, ...sides.left },
|
|
right: { ...DEFAULT_BOX_SIDE_ADJUST, ...sides.right },
|
|
};
|
|
}
|
|
}
|
|
|
|
/** 관 옵션 조정값 몫 얹기(387-4b) — 관 초안 ?? 저장 관(페이지 자료 줄). 가게 · 자료 줄은 부를 때 늦게 불러옴 —
|
|
* 이 파일을 끄는 시험 · 묶음이 그 줄기(fetch · 가게)를 안 끌게. 못 받으면 그대로.
|
|
* `reshape` = 관 제원도 형상 얹기 규칙으로 다시(다시 셈 창구 앞 · B06 폼 되보내기가 옵션 값을 제원에 그대로
|
|
* 써 넣어 최소 높이 · 집수정 쪽 칸이 서버와 갈림 — 집수정 높이 1.5 ↔ 2 등). */
|
|
export async function withPipeOptions(
|
|
detail: SectionDetailResponse,
|
|
projectId: string,
|
|
reshape = false,
|
|
): Promise<void> {
|
|
if (reshape)
|
|
await import("./B06_Section_Section_Drafts")
|
|
.then(({ withDraftShapes }) => withDraftShapes(detail, projectId))
|
|
.catch(() => undefined);
|
|
const pipes = await import("../B05_Profile/B05_Profile_Api_Pipes_Draft")
|
|
.then(
|
|
async ({ readPendingPipes }) =>
|
|
readPendingPipes(projectId) ??
|
|
(await import("../A00_Common/design/design_page_data"))
|
|
.pagePipes(projectId)
|
|
.then((res) => res.pipe_points),
|
|
)
|
|
.catch(() => null);
|
|
if (pipes) layPipeOptions(detail, pipes as OptionPipe[]);
|
|
}
|
|
|
|
/** 관 하나 — 자리 · 폼 칸(관 초안 · 저장 관 `pipe_points` 같은 꼴). */
|
|
export interface OptionPipe {
|
|
chainage_m: number;
|
|
options?: Record<string, unknown> | null;
|
|
}
|
|
|
|
const isDefault = (a: WallAdjust): boolean =>
|
|
Math.abs(a.x) < 1e-9 && a.d == null && a.h == null && a.m == null;
|
|
const round1 = (value: number): number => Math.round(value * 10) / 10;
|
|
|
|
/** 조정창 `update` 와 같은 꼴 — x · d 는 그대로 · h · m 만(폼 한 칸이 주인 · 빈 칸 = 자동 null). */
|
|
function setWall(design: CrossDesign, role: string, h: number | null, m: string | null): void {
|
|
const current = { ...ZERO_ADJUST, ...(design.revet_adjust?.[role] ?? {}) } as WallAdjust;
|
|
const next: WallAdjust = { ...current, h: h === null ? null : round1(h), m };
|
|
const bucket = { ...(design.revet_adjust ?? {}) };
|
|
if (isDefault(next)) delete bucket[role];
|
|
else bucket[role] = next;
|
|
design.revet_adjust = bucket;
|
|
}
|
|
|
|
/** 관 옵션의 조정값 몫을 상세에 얹음(폼 칸 전부 = 되보내기와 같은 규칙) — 같은 값이면 그대로. */
|
|
export function layPipeOptions(
|
|
detail: SectionDetailResponse,
|
|
pipes: ReadonlyArray<OptionPipe>,
|
|
): void {
|
|
const sections = detail.cross_sections;
|
|
for (const pipe of pipes) {
|
|
const options = pipe.options;
|
|
if (!options) continue;
|
|
const near = (at: number): boolean => Math.abs(at - pipe.chainage_m) < PIPE_MATCH_M;
|
|
// 물넘이포장 = 제원만(66-2) · 벽 없음
|
|
if (sections.some((s) => s.ford_pavement && near(s.ford_pavement.chainage_m ?? s.chainage_m)))
|
|
continue;
|
|
const owner = sections.find((s) => near(s.chainage_m));
|
|
const culvert = owner?.culvert;
|
|
const design = owner?.design;
|
|
if (!owner || !culvert || !design) continue;
|
|
const num = (key: string): number | undefined => {
|
|
const value = Number(options[key]);
|
|
return Number.isFinite(value) ? value : undefined;
|
|
};
|
|
for (const role of ["inlet", "outlet"] as const) {
|
|
const form = options[`${role}_revet_form`];
|
|
setWall(
|
|
design,
|
|
role,
|
|
num(`${role}_revet_height_m`) ?? null,
|
|
typeof form === "string" ? form : null,
|
|
);
|
|
}
|
|
if (culvert.hidden_pipe && !owner.ford && !owner.box) continue;
|
|
// 흙막이 n 단 = 지형이 세운 유출 다단(`pipeExtraCount` 와 같은 셈)
|
|
const outlet = design.extra_wall_counts?.outlet ?? 0;
|
|
const probe = {
|
|
...owner,
|
|
design: { ...design, extra_wall_counts: { basin: 0, ...design.extra_wall_counts, outlet } },
|
|
};
|
|
const tiers = outlet
|
|
? (computeStoredLayouts(probe, sections)?.culvert?.extraWalls.length ?? outlet)
|
|
: 0;
|
|
for (let n = 1; n <= tiers; n++) {
|
|
const role = `extra${n - 1}`;
|
|
const value = (item: string): number | undefined => {
|
|
const raw = options[`soil_add_${n}_${item}`];
|
|
return raw === undefined || raw === "" || !Number.isFinite(Number(raw))
|
|
? undefined
|
|
: Number(raw);
|
|
};
|
|
const form = options[`soil_add_${n}_form`];
|
|
const height = value("height");
|
|
setWall(
|
|
design,
|
|
role,
|
|
height === undefined ? null : Number(Math.max(height, WALL_MIN_PURE_HEIGHT_M).toFixed(1)),
|
|
typeof form === "string" && form ? form : null,
|
|
);
|
|
const span = { lengthM: value("length"), beforeM: value("before"), afterM: value("after") };
|
|
if (Object.values(span).every((v) => v === undefined)) continue;
|
|
const at = tierSpanOf(owner, role);
|
|
const current = {
|
|
lengthM: Math.round((at.beforeM + at.afterM) * 10) / 10,
|
|
beforeM: Math.round(at.beforeM * 10) / 10,
|
|
afterM: Math.round(at.afterM * 10) / 10,
|
|
};
|
|
const next = applySpanPatch(current, span);
|
|
design.extra_spans = {
|
|
...(design.extra_spans ?? {}),
|
|
[role]: { length_m: next.lengthM, before_m: next.beforeM, after_m: next.afterM },
|
|
};
|
|
}
|
|
}
|
|
}
|