From e6869d17be1be77ce1e397fadd7d166292f5191d Mon Sep 17 00:00:00 2001 From: ypshim Date: Wed, 4 Nov 2026 17:57:15 +0900 Subject: [PATCH] =?UTF-8?q?feat(abut):=20=EB=82=A0=EA=B0=9C=EB=B2=BD=20?= =?UTF-8?q?=EB=B0=B0=EA=B7=BC=20=EC=9E=85=EB=A0=A5=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?(=EC=A2=8C=EC=9A=B0=20=EA=B3=B5=ED=86=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AbutRecord.rebar.wing (바꾼 값만), applyWingRebar, 프로젝트 파일 검증 Co-Authored-By: Claude Haiku 5.5 --- src/core/abut/edit.ts | 11 +++++++++++ src/core/abut/parse.ts | 3 ++- src/core/project.ts | 3 +++ tests/abut-rebar-wing.test.ts | 18 ++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/core/abut/edit.ts b/src/core/abut/edit.ts index f651ed8..d39b04f 100644 --- a/src/core/abut/edit.ts +++ b/src/core/abut/edit.ts @@ -11,6 +11,7 @@ import type { AbutGround } from './ground'; import { footingRebarIssues, resolveFootingRebar, type AbutFootingRebarSettings } from './rebar/footing'; 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 { abutBearingRuleIssues, resolveBearingRule, type AbutBearingRule } from './bearing-table'; export type AbutEditKey = 'seatEl' | 'foundationBottomEl' | 'groundEl' | 'pileLengthMm' | 'skewDeg' | 'slopeI'; @@ -200,6 +201,16 @@ export function applyParapetRebar(rec: AbutRecord, patch: Partial): AbutRecord { + for (const v of Object.values(patch)) if (typeof v === 'number' && !Number.isFinite(v)) throw new Error('숫자를 입력하세요.'); + const wing: Partial = { ...(rec.rebar?.wing ?? {}), ...patch }; + for (const key of Object.keys(wing) as (keyof AbutWingRebarSettings)[]) if (wing[key] === undefined) delete wing[key]; + const issues = wingRebarIssues(resolveWingRebar(wing)); + if (issues.length > 0) throw new Error(issues[0]); + return { ...rec, rebar: { ...(rec.rebar ?? {}), wing } }; +} + /** 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 155f852..c763728 100644 --- a/src/core/abut/parse.ts +++ b/src/core/abut/parse.ts @@ -18,6 +18,7 @@ import type { AbutGround } from './ground'; import type { AbutFootingRebarSettings } from './rebar/footing'; import type { AbutWallRebarSettings } from './rebar/wall'; import type { AbutParapetRebarSettings } from './rebar/parapet'; +import type { AbutWingRebarSettings } from './rebar/wing'; type Cell = string | number | null | undefined; type Grid = Cell[][]; @@ -69,7 +70,7 @@ export interface AbutRecord { /** 지반·지층 입력 (② 지반·지층 카드, 교각과 같은 필드). Absent = 육상, 지하수위 없음, 지층 행 없음. */ ground?: AbutGround; /** ③ 배근 입력 (부재별, 바꾼 값만). Absent = 엑셀 Abut 시트 기본값. */ - rebar?: { footing?: Partial; wall?: Partial; parapet?: Partial }; + rebar?: { footing?: Partial; wall?: Partial; parapet?: Partial; wing?: Partial }; /** 예전 "좌우 같게" 값 (2026-11-04 이전 파일). 지금은 읽기만 하고 쓰지 않는다. */ wingsLinked?: boolean; } diff --git a/src/core/project.ts b/src/core/project.ts index 4345ae3..e78f46f 100644 --- a/src/core/project.ts +++ b/src/core/project.ts @@ -174,6 +174,9 @@ export function checkStoredAbutments(list: unknown): void { for (const [key, v] of Object.entries(a.rebar.parapet ?? {})) { if (!finite(v)) throw new Error(`${who}: 흉벽 배근 값 ${key}이(가) 맞지 않습니다.`); } + for (const [key, v] of Object.entries(a.rebar.wing ?? {})) { + 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-wing.test.ts b/tests/abut-rebar-wing.test.ts index 97d3cf7..87efcfa 100644 --- a/tests/abut-rebar-wing.test.ts +++ b/tests/abut-rebar-wing.test.ts @@ -90,3 +90,21 @@ describe('교대 날개벽 배근 A1 좌 (AD7, 교대날개벽철근.md §11)', expect(bar(r, 'WR3')!.modified).toBe(true); }); }); + +import { applyWingRebar } 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 = applyWingRebar(rec, { sHoriz: 150, delta: 30 }); + expect(a.rebar?.wing).toEqual({ sHoriz: 150, delta: 30 }); + expect(applyWingRebar(a, { sHoriz: undefined }).rebar?.wing).toEqual({ delta: 30 }); + expect(() => applyWingRebar(rec, { k20: 0 })).toThrow('WR20 배수'); + expect(() => applyWingRebar(rec, { sH: -1 })).toThrow(); + }); + + it('the project file keeps the 날개벽 input and refuses a non-number', () => { + expect(() => checkStoredAbutments([applyWingRebar(rec, { c: 80 })])).not.toThrow(); + expect(() => checkStoredAbutments([{ ...rec, rebar: { wing: { c: 'x' } } as never }])).toThrow('날개벽 배근'); + }); +});