diff --git a/src/core/abut/edit.ts b/src/core/abut/edit.ts index d859be6..f8e0186 100644 --- a/src/core/abut/edit.ts +++ b/src/core/abut/edit.ts @@ -13,6 +13,7 @@ import { resolveWallRebar, wallRebarIssues, type AbutWallRebarSettings } from '. 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 { blockRebarIssues, resolveBlockRebar, type AbutBlockSettings } from './rebar/block'; import { abutBearingRuleIssues, resolveBearingRule, type AbutBearingRule } from './bearing-table'; export type AbutEditKey = 'seatEl' | 'foundationBottomEl' | 'groundEl' | 'pileLengthMm' | 'skewDeg' | 'slopeI'; @@ -222,6 +223,16 @@ export function applySlabRebar(rec: AbutRecord, patch: Partial): AbutRecord { + for (const v of Object.values(patch)) if (typeof v === 'number' && !Number.isFinite(v)) throw new Error('숫자를 입력하세요.'); + const block: Partial = { ...(rec.rebar?.block ?? {}), ...patch }; + for (const key of Object.keys(block) as (keyof AbutBlockSettings)[]) if (block[key] === undefined) delete block[key]; + const issues = blockRebarIssues(resolveBlockRebar(block)); + if (issues.length > 0) throw new Error(issues[0]); + return { ...rec, rebar: { ...(rec.rebar ?? {}), block } }; +} + /** 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('숫자를 입력하세요.'); diff --git a/src/core/abut/parse.ts b/src/core/abut/parse.ts index 7cd38d3..25dcd95 100644 --- a/src/core/abut/parse.ts +++ b/src/core/abut/parse.ts @@ -20,6 +20,7 @@ import type { AbutWallRebarSettings } from './rebar/wall'; import type { AbutParapetRebarSettings } from './rebar/parapet'; import type { AbutWingRebarSettings } from './rebar/wing'; import type { AbutSlabRebarSettings } from './rebar/slab'; +import type { AbutBlockSettings } from './rebar/block'; type Cell = string | number | null | undefined; type Grid = Cell[][]; @@ -71,7 +72,7 @@ export interface AbutRecord { /** 지반·지층 입력 (② 지반·지층 카드, 교각과 같은 필드). Absent = 육상, 지하수위 없음, 지층 행 없음. */ ground?: AbutGround; /** ③ 배근 입력 (부재별, 바꾼 값만). Absent = 엑셀 Abut 시트 기본값. */ - rebar?: { footing?: Partial; wall?: Partial; parapet?: Partial; wing?: Partial; slab?: Partial }; + rebar?: { footing?: Partial; wall?: Partial; parapet?: Partial; wing?: Partial; slab?: Partial; block?: Partial }; /** 예전 "좌우 같게" 값 (2026-11-04 이전 파일). 지금은 읽기만 하고 쓰지 않는다. */ wingsLinked?: boolean; } diff --git a/src/core/project.ts b/src/core/project.ts index 1d6fb31..d974b8e 100644 --- a/src/core/project.ts +++ b/src/core/project.ts @@ -180,6 +180,9 @@ export function checkStoredAbutments(list: unknown): void { for (const [key, v] of Object.entries(a.rebar.slab ?? {})) { if (!finite(v)) throw new Error(`${who}: 접속슬래브 배근 값 ${key}이(가) 맞지 않습니다.`); } + for (const [key, v] of Object.entries(a.rebar.block ?? {})) { + if (!finite(v)) throw new Error(`${who}: 받침블럭 배근 값 ${key}이(가) 맞지 않습니다.`); + } } if (a.wingsLinked !== undefined && typeof a.wingsLinked !== 'boolean') throw new Error(`${who}: 좌우 같게 값이 맞지 않습니다.`); if (a.wings !== undefined) { diff --git a/tests/abut-rebar-block.test.ts b/tests/abut-rebar-block.test.ts index 3eebb0d..fe0f763 100644 --- a/tests/abut-rebar-block.test.ts +++ b/tests/abut-rebar-block.test.ts @@ -50,3 +50,25 @@ describe('교대 받침블럭 배근 A1 (AD6, 교대 기획서 §10)', () => { expect(len).toBeCloseTo(1100, 6); }); }); + +import { applyBlockRebar } from '../src/core/abut/edit'; +import { checkStoredAbutments } from '../src/core/project'; +import { parseAbutments } from '../src/core/abut/parse'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import * as XLSX from 'xlsx'; + +describe('③ 받침블럭 배근 입력 저장', () => { + const rec = parseAbutments(XLSX.read(readFileSync(fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url))), { type: 'buffer' })).abutments[0]; + it('stores only the changed values; undefined links back; refuses bad values', () => { + const a = applyBlockRebar(rec, { spacingS1: 150, legLength: 800 }); + expect(a.rebar?.block).toEqual({ spacingS1: 150, legLength: 800 }); + expect(applyBlockRebar(a, { spacingS1: undefined }).rebar?.block).toEqual({ legLength: 800 }); + expect(() => applyBlockRebar(rec, { legLength: 0 })).toThrow(); + }); + + it('the project file keeps the 받침블럭 input and refuses a non-number', () => { + expect(() => checkStoredAbutments([applyBlockRebar(rec, { coverTop: 60 })])).not.toThrow(); + expect(() => checkStoredAbutments([{ ...rec, rebar: { block: { coverTop: 'x' } } as never }])).toThrow('받침블럭 배근'); + }); +});