전수조사 1단계 — 저장된 서식을 오염시키는 결함 7건 수정
레거시 대비 전수조사에서 나온 출시 차단 9건 중, 하루 안에 끝나는 국소 수정 7건. 저장 데이터를 망가뜨리는 ③④ 를 먼저 고쳤다. ③ 페이지 복제·추가가 루트 이름을 중복 발급했다 Clone 이 Root.Id 까지 베끼는데 자식만 재발급해서 한 서식에 MDesignerHost1 이 둘 생겼다. AddPage 도 '페이지 수 + 1' 로 지어서 루트가 1·3 인 2장짜리 서식에서 곧바로 3 이 겹쳤다. E_SctMst 동기화는 SctObjNam 사전이라 둘째 루트가 첫째 루트 행에 매핑되고, 그 페이지 자식들의 SctParObj 가 없는 ObjID 를 가리킨 채 남는다. → 두 경로 모두 RenamePageRoot 로 이름과 Name 속성을 함께 새로 받는다. ④ 이름 채번이 빈 번호를 메웠다 TextBox3 을 지우고 새로 놓으면 다시 TextBox3 이 나왔다. 이름은 저장된 답변 (E_SctDta.SctObjNam)이 붙는 열쇠라서, 지운 칸의 과거 입력이 새 칸에 그대로 나타난다. 액션 대상과 CalcBox 수식도 이름으로 서로를 가리키므로 조용히 오배선된다. → 레거시 clsNameCreationService 규약대로 '사용 중 최대번호 + 1'. 숫자가 아닌 접미사(TextBoxTotal)는 번호로 보지 않는다. ① 탭순서 적용이 문서 전체를 밀었다 한 페이지를 편집해도 전 페이지 컨트롤이 999999+i 로 초기화됐다. 레거시는 페이지마다 별도 디자인 서피스라 다른 페이지는 건드리지 않는다. → 초기화 범위를 이번에 클릭한 페이지로 한정. ② 이력본이 열린 상태에서 현재본을 열면 이력 탭이 활성화됐다 FindOpenDbDocument 가 ShtCod 만 보고 있었다. 사용자는 현재본을 고친다고 믿은 채 읽기 전용 스냅샷을 편집하게 된다. → HistorySdgKey is null 조건 추가. ⑤ 레거시에 없는 태그 3종을 고를 수 있었다 PAT_BMI · PAT_IBW · PAT_IBW_퍼센트 는 bzDataInterface.vb:922/957/970 에서 주석 처리된 채로 남아 있어 런타임에 해석되지 않는다. 고르면 배포 후에야 빈칸으로 드러난다. → 카탈로그에서 제거(값은 자유 입력이라 기존 서식에는 영향 없다). ⑥ 신규 서식 등록이 ShtHspYon·ShtExpDte 를 비웠다 --db-shtmst 진단을 신설해 실측한 결과 운영 E_ShtMst 1,737행 중 두 컬럼이 NULL 인 행은 0건이다. ShtExpDte 는 dtEMRLoader.vb:1229 가 NVL(ShtExpDte,' ')='29991231' 로 못박아 거르고, 여러 조회가 sysdate BETWEEN ShtAdpDte AND ShtExpDte 를 쓴다 — NULL 이면 BETWEEN 이 거짓이라 등록은 성공했는데 EMR 어디에서도 안 보이는 서식이 된다(29991231 이 1,718행 98.9%). ShtHspYon 은 기록지정보·자동생성 목록이 'Y' 로 거르며, 디자이너가 만든 서식 169건 중 160건이 'Y'. → 두 값을 INSERT 에 넣는다. ⑦ 빈 선택 상태의 Ctrl+드래그가 예외로 죽었다 Ctrl/Shift 클릭은 선택을 바꾸지 않고 업에서 토글하므로 주선택이 빈 채 드래그가 시작될 수 있는데, BeginMove 가 Selection.Primary! 를 그대로 넘겨 RootAncestorOf 가 즉시 역참조했다. → 앵커를 Primary ?? 첫 선택으로 완화. 편집 스모크 9건 추가(루트 이름 유일 2 · 채번 4 · 탭순서 페이지 격리 1 · 빈 선택 드래그 1 · 태그 3종 부재 1). 채번 검사는 '가운데 번호'를 지운다 — 최대 번호를 지우면 최대값 자체가 내려가므로 같은 이름이 다시 나오는 것이 레거시와 같은 정상 동작이다. 회귀: 테스트 124/124, 편집 스모크 169건 실패 0, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일(HEAD 대비 md5 일치). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fdb1476b0b
commit
8eeff42a72
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using SheetMe.Core.Models;
|
||||
|
||||
namespace SheetMe.Designer.Services;
|
||||
@@ -17,17 +18,39 @@ public static class IdGenerator
|
||||
return used;
|
||||
}
|
||||
|
||||
/// <summary>타입 기반 유일 Id 생성 — 발급한 Id 는 used 집합에 추가된다(연속 발급 안전)</summary>
|
||||
/// <summary>
|
||||
/// 타입 기반 유일 Id 생성 — 같은 타입의 <b>최대 번호 + 1</b>(레거시 clsNameCreationService 규약).
|
||||
/// 발급한 Id 는 used 집합에 추가된다(연속 발급 안전).
|
||||
///
|
||||
/// 빈 번호를 메우지 않는 것이 핵심이다. TextBox3 을 지우고 새 TextBox 를 놓으면
|
||||
/// 최소 빈칸 방식은 다시 TextBox3 을 발급하는데, 이름은 저장된 답변(E_SctDta.SctObjNam)이
|
||||
/// 붙는 열쇠라서 <b>지운 칸의 과거 입력이 새 칸에 그대로 나타난다</b>. 액션 대상과
|
||||
/// CalcBox 수식도 이름으로 서로를 가리키므로 같은 이름을 재사용하면 조용히 오배선된다.
|
||||
/// </summary>
|
||||
public static string NextId(HashSet<string> used, string type)
|
||||
{
|
||||
for (var i = 1; ; i++)
|
||||
var next = 1;
|
||||
foreach (var id in used)
|
||||
{
|
||||
var candidate = type + i;
|
||||
if (used.Add(candidate))
|
||||
if (id.Length <= type.Length || !id.StartsWith(type, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return candidate;
|
||||
continue;
|
||||
}
|
||||
// 접미사가 순수 숫자일 때만 번호로 본다("TextBox1a" 나 "TextBoxTotal" 은 무시)
|
||||
if (int.TryParse(id.AsSpan(type.Length), NumberStyles.None, CultureInfo.InvariantCulture, out var number)
|
||||
&& number >= next)
|
||||
{
|
||||
next = number + 1;
|
||||
}
|
||||
}
|
||||
var candidate = type + next.ToString(CultureInfo.InvariantCulture);
|
||||
// 방어: "TextBox0" 같은 값이 이미 있으면 next 가 1 로 남아 충돌할 수 있다
|
||||
while (!used.Add(candidate))
|
||||
{
|
||||
next++;
|
||||
candidate = type + next.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/// <summary>단건 발급 편의 — 문서 스캔 후 1개 생성</summary>
|
||||
|
||||
@@ -282,7 +282,10 @@ public sealed class InteractionController
|
||||
private void BeginMove()
|
||||
{
|
||||
CaptureOrigBounds();
|
||||
var page = designer.PageOf(designer.Selection.Primary!) ?? designer.Pages.FirstOrDefault();
|
||||
// Primary 는 없을 수 있다(선택 항목은 있는데 주선택이 비는 경로가 있다) —
|
||||
// ?! 로 넘기면 PageOf → RootAncestorOf 가 즉시 역참조해 이동 시작이 예외로 죽는다
|
||||
var anchor = designer.Selection.Primary ?? designer.Selection.Items.FirstOrDefault();
|
||||
var page = (anchor is null ? null : designer.PageOf(anchor)) ?? designer.Pages.FirstOrDefault();
|
||||
if (page is not null)
|
||||
{
|
||||
designer.Snap.BeginDrag(page, designer.Selection.Items.ToHashSet());
|
||||
|
||||
Reference in New Issue
Block a user