feat(abut): 접속슬래브 배근 입력 저장

- AbutRecord.rebar.slab (바꾼 값만), applySlabRebar, 프로젝트 파일 검증

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-05 09:23:56 +09:00
1 parent fa7d0032c3
commit 9ae6cf0945
4 files changed
+34 -1

No files matched your search

+11
View File
@@ -12,6 +12,7 @@ import { footingRebarIssues, resolveFootingRebar, type AbutFootingRebarSettings
import { resolveWallRebar, wallRebarIssues, type AbutWallRebarSettings } from './rebar/wall';
import { parapetRebarIssues, resolveParapetRebar, type AbutParapetRebarSettings } from './rebar/parapet';
import { resolveWingRebar, wingRebarIssues, type AbutWingRebarSettings } from './rebar/wing';
import { resolveSlabRebar, slabRebarIssues, type AbutSlabRebarSettings } from './rebar/slab';
import { abutBearingRuleIssues, resolveBearingRule, type AbutBearingRule } from './bearing-table';
export type AbutEditKey = 'seatEl' | 'foundationBottomEl' | 'groundEl' | 'pileLengthMm' | 'skewDeg' | 'slopeI';
@@ -211,6 +212,16 @@ export function applyWingRebar(rec: AbutRecord, patch: Partial<AbutWingRebarSett
return { ...rec, rebar: { ...(rec.rebar ?? {}), wing } };
}
/** Changes ③ 접속슬래브 배근 values (only the changed ones are kept; undefined clears one), refused with a Korean message. */
export function applySlabRebar(rec: AbutRecord, patch: Partial<AbutSlabRebarSettings>): AbutRecord {
for (const v of Object.values(patch)) if (typeof v === 'number' && !Number.isFinite(v)) throw new Error('숫자를 입력하세요.');
const slab: Partial<AbutSlabRebarSettings> = { ...(rec.rebar?.slab ?? {}), ...patch };
for (const key of Object.keys(slab) as (keyof AbutSlabRebarSettings)[]) if (slab[key] === undefined) delete slab[key];
const issues = slabRebarIssues(resolveSlabRebar(slab));
if (issues.length > 0) throw new Error(issues[0]);
return { ...rec, rebar: { ...(rec.rebar ?? {}), slab } };
}
/** Changes one 접속슬래브 dim, refused with a Korean message when the slab would not build. */
export function applySlabEdit(rec: AbutRecord, key: (typeof SLAB_EDIT_FIELDS)[number]['key'], value: number): AbutRecord {
if (!Number.isFinite(value)) throw new Error('숫자를 입력하세요.');
+2 -1
View File
@@ -19,6 +19,7 @@ import type { AbutFootingRebarSettings } from './rebar/footing';
import type { AbutWallRebarSettings } from './rebar/wall';
import type { AbutParapetRebarSettings } from './rebar/parapet';
import type { AbutWingRebarSettings } from './rebar/wing';
import type { AbutSlabRebarSettings } from './rebar/slab';
type Cell = string | number | null | undefined;
type Grid = Cell[][];
@@ -70,7 +71,7 @@ export interface AbutRecord {
/** 지반·지층 입력 (② 지반·지층 카드, 교각과 같은 필드). Absent = 육상, 지하수위 없음, 지층 행 없음. */
ground?: AbutGround;
/** ③ 배근 입력 (부재별, 바꾼 값만). Absent = 엑셀 Abut 시트 기본값. */
rebar?: { footing?: Partial<AbutFootingRebarSettings>; wall?: Partial<AbutWallRebarSettings>; parapet?: Partial<AbutParapetRebarSettings>; wing?: Partial<AbutWingRebarSettings> };
rebar?: { footing?: Partial<AbutFootingRebarSettings>; wall?: Partial<AbutWallRebarSettings>; parapet?: Partial<AbutParapetRebarSettings>; wing?: Partial<AbutWingRebarSettings>; slab?: Partial<AbutSlabRebarSettings> };
/** 예전 "좌우 같게" 값 (2026-11-04 이전 파일). 지금은 읽기만 하고 쓰지 않는다. */
wingsLinked?: boolean;
}
+3
View File
@@ -177,6 +177,9 @@ export function checkStoredAbutments(list: unknown): void {
for (const [key, v] of Object.entries(a.rebar.wing ?? {})) {
if (!finite(v)) throw new Error(`${who}: 날개벽 배근 값 ${key}이(가) 맞지 않습니다.`);
}
for (const [key, v] of Object.entries(a.rebar.slab ?? {})) {
if (!finite(v)) throw new Error(`${who}: 접속슬래브 배근 값 ${key}이(가) 맞지 않습니다.`);
}
}
if (a.wingsLinked !== undefined && typeof a.wingsLinked !== 'boolean') throw new Error(`${who}: 좌우 같게 값이 맞지 않습니다.`);
if (a.wings !== undefined) {
+18
View File
@@ -106,3 +106,21 @@ describe('교대 접속슬래브 배근 A1 (AD8, 교대접속슬래브철근.md
expect(r3.pts.length).toBeGreaterThan(2); // 헌치 · 벽체 배면에서 꺾임
});
});
import { applySlabRebar } from '../src/core/abut/edit';
import { checkStoredAbutments } from '../src/core/project';
describe('③ 접속슬래브 배근 입력 저장', () => {
it('stores only the changed values; undefined links a group back; refuses bad values', () => {
const a = applySlabRebar(rec, { sMain: 200, k8: 6 });
expect(a.rebar?.slab).toEqual({ sMain: 200, k8: 6 });
expect(applySlabRebar(a, { sMain: undefined }).rebar?.slab).toEqual({ k8: 6 });
expect(() => applySlabRebar(rec, { k8: 1.5 })).toThrow('R8 배수');
expect(() => applySlabRebar(rec, { cv: 0 })).toThrow();
});
it('the project file keeps the 접속슬래브 input and refuses a non-number', () => {
expect(() => checkStoredAbutments([applySlabRebar(rec, { c: 90 })])).not.toThrow();
expect(() => checkStoredAbutments([{ ...rec, rebar: { slab: { c: 'x' } } as never }])).toThrow('접속슬래브 배근');
});
});