Commit Graph
11 Commits
Author SHA1 Message Date
MsystechandClaude Fable 5 68f0fbd5a3 마스크 편집기 — 프리셋 목록 + 실시간 미리보기
레거시 MaskedTextBox 는 System.Windows.Forms.MaskedTextBox 를 상속하고 Mask 를 재정의하지 않아
WinForms 기본 마스크 디자이너(프리셋 목록 + 시험 입력)를 그대로 물려받는다. SheetMe 인스펙터는
평범한 텍스트 상자뿐이라 마스크 문법을 외워야 했다.

[프리셋] LegacyMaskCatalog — .NET 문화권 기본값(짧은 날짜/전화번호 등) 대신 운영 활성 디자인
전수 census 에서 실제로 쓰이는 마스크를 사용 건수와 함께 제공한다. 기존 서식과 같은 표기를
그대로 고를 수 있어 표기 흔들림이 줄어든다. 날짜/시간/날짜+시간/번호 그룹 + 표준 3종.

[미리보기] 두 줄로 보여준다.
  빈 값 표시 — 마스크만으로 화면에 어떻게 보이는지(ToPromptDisplay)
  시험 입력 — 값을 쳐 보면 어떻게 채워지는지(Apply, 신규)
인스펙터 행에도 '빈 값 표시'를 상시 노출해 편집기를 열지 않아도 확인된다.

[Apply 규칙] 구현 중 모호한 경우가 드러나 규칙을 단순하게 확정했다.
  1) 리터럴은 항상 그대로 출력되고 입력을 소비하지 않는다.
  2) 지시자는 입력에서 자기에게 맞는 첫 문자를 가져오며, 맞지 않는 문자와 공백은 건너뛴다.
값만 치든(20260812) 구분자를 같이 치든(2026-08-12, 2026년 08월 12일) 결과가 같아진다.
공백을 건너뛰는 이유는 '9' 가 공백도 받아들이기 때문이다 — 그대로 두면 구분자 공백이 데이터
자리를 먹어 한 칸씩 밀린다. '9' 의 공백 허용은 빈 자리를 위한 것이고 그건 프롬프트 문자가 이미
표현한다.
한계를 코드에 명시했다: 리터럴이 숫자인 마스크(2\000년 — 운영 460건 중 1건)는 사용자가
리터럴까지 치면 어디까지가 데이터인지 원리적으로 가릴 수 없다. 편집 가능한 자리만 치면 된다.
미리보기용이므로 휴리스틱을 쌓기보다 규칙을 단순하게 두는 편이 낫다고 판단했다.

안내문에서 백슬래시를 글리프로 보여주지 않는다 — 한글 글꼴에서 원화 기호로 렌더되어 오해를 부른다.

검증: 테스트 124/124(마스크 26건 추가 — Apply 자리 채움/구분자 무시/유효성 거부/대소문자 변환자/
이스케이프/공백 처리, 프리셋 형식·중복·운영 다빈도 포함 검사). edit-smoke 실패 0,
왕복 1,271건 diff 0/예외 0. 실제 UI 로 대화상자 열어 프리셋 그룹·건수·두 줄 미리보기 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 09:38:05 +09:00
MsystechandClaude Fable 5 a749e18e7b MaskedTextBox 를 레거시와 동일하게 프롬프트 문자로 표시
4단계 육안 대조에서 발견한 차이. 마스크 패턴을 그대로 그려서 값이 이미 입력된 것처럼 보였다.

    레거시    ___년 __월 __일   (프롬프트 문자)
    SheetMe   0000년 90월 90일  (마스크 패턴 원문)

저장 데이터와는 무관한 표시 문제지만, 운영 실사용이 MaskedTextBox 475개 / Mask 보유 460개라
자주 눈에 띈다.

LegacyMask.ToPromptDisplay 신설(Core, 순수 함수) — 입력 자리 지시자는 프롬프트 문자로,
리터럴은 그대로, 이스케이프(\)는 다음 문자를 리터럴로, 대소문자 변환자(< > |)는 표시하지 않는다.
활성 디자인 전수 census 로 지원 범위를 정했다: 등장 지시자는 0(2,675) 9(687) #(12) 와
이스케이프 \(2) 뿐이고 문자류(L ? & C A a)와 변환자는 쓰이지 않는다. PromptChar 속성을 가진
컨트롤은 0개라 전부 기본값 '_' 이지만, 속성이 있으면 반영하도록 구현했다.

구분자 . , : / $ 는 WinForms 가 문화권 구분자로 치환하지만 원문 그대로 둔다 —
운영 마스크는 보이는 대로 쓰이고 레거시 화면도 그렇게 나온다.

MaskDisplay 는 표시 전용 파생 속성이라 PropBag 에 아무것도 쓰지 않는다(왕복 무손실 유지).
인스펙터 편집용 Mask 원문 속성은 그대로 둔다.

검증: 테스트 98/98(마스크 28건 추가 — 운영 실사용 마스크 10종, 이스케이프, 문자류 지시자,
변환자, PromptChar 지정, 끝 백슬래시, 표시 길이 보존). S481 재렌더로 레거시와 동일 표시 확인.
edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 09:20:30 +09:00
MsystechandClaude Fable 5 d1e488625e 육안 검증 준비 — 전 페이지 렌더 + 제자리 갱신 E_SctMst 어서션 정정
[전 페이지 렌더] --db-render 가 첫 페이지만 뽑던 것을 전 페이지로 확장했다(이름_p1.png …).
운영 서식 128건이 다중 페이지라 첫 장만 봐서는 검증이 되지 않는다.

[E_SctMst 어서션 정정] --db-save-smoke 가 제자리 갱신 경로에서도 sctCount == walkerCount 를
요구하고 있었는데, 이 불변식은 버저닝 경로에만 성립한다.
제자리 갱신은 레거시와 동일하게 삭제된 컨트롤의 행을 지우지 않으므로(SctKey 안정성) 과거에
지워진 컨트롤의 고아 행이 누적된다. P347 에서 실제로 걸렸다 — E_SctMst 3행(MDesignerHost1,
TextBox1, TextBox2) 대 현재 XML 2개(MDesignerHost1, TextBox2)로, TextBox1 은 과거에 지워진
컨트롤의 잔존 행이었다. 기존 데이터이지 저장 경로의 결함이 아니다.
제자리 갱신은 "워커 산출 행이 전부 존재"(sctCount >= walkerCount)로 바꾸고 잔존 행 수를
리포트에 표시한다. 버저닝은 전량 재생성이므로 정확히 일치 요구를 유지한다.

[검증 준비 결과] 위험도 상위 16건을 SheetMe 로 재저장하고 저장 전/후 렌더를 픽셀 비교했다 —
39페이지 전부 동일(J209 6장, S256 7장 포함). 대상 선정은 운영 활성 디자인을 전수 스캔해
위험 요소로 가중치를 매겼다: 자리표시 15건 · Spread 7건 · Binary 569건(45%) ·
배경이미지 315건 · 다중페이지 128건 · 제자리갱신 36건.

레거시 뷰어 대조는 사용자가 직접 수행한다(공용 ServerInfo ini 의 CurrentServer 를 운영에서
테스트로 바꿔야 하는데, 그 파일을 레거시 EXE 187개가 공유하므로 자동화하지 않았다).
바탕화면 'SheetMe-검증패키지' 에 체크리스트·렌더 PNG 39장·자동검증 리포트를 준비했다.

검증: 테스트 70/70, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0,
db-save-smoke 제자리 갱신(P163 정상/P347 잔존 1건 정상)·버저닝(S999) 양쪽 통과.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 08:57:26 +09:00
MsystechandClaude Fable 5 71862c2986 배포 준비 — 자격증명 분리, 산출물 축소, 로깅·전역 예외
[자격증명] 접속 문자열 우선순위를 환경변수 > appsettings.Development.json > appsettings.json >
ServerInfo ini 로 세웠다. 병원 설치본은 설정 없이 [000]bin 의 MSYSTECH_ServerInfo.ini 를
자동으로 찾아 붙는다(레거시와 동일한 MD5 → 3DES-ECB 복호).
- ServerInfoReader 를 [200]SheetMe 에서 복사 이식(vendoring — 두 저장소가 분리돼 있어
  ProjectReference 불가, 레거시 암복호 규약은 변할 이유가 없는 고정 자산). 헤더에 동기화 의무 명시.
- 실측 검증: ini 566라인/병원 엔트리 76개, CurrentServer=029.BSGH 복호 후 실제 접속 성공.
- SaveMode 기본값을 File 로 되돌렸다. ini 의 CurrentServer 가 운영 병원 DB 를 가리키므로
  DB 쓰기는 명시적으로 켠 단말에서만 활성화되어야 한다.
- ConfigService 로 단일 소유화 — ConfigLoader.Load() 11회 호출이 ini 파싱 + 3DES 복호를
  매번 반복하던 것을 1회로. 죽어 있던 Designer:GridSize/SnapThreshold 를 SnapEngine 에 배선하고,
  쓰이지 않던 His:Provider 와 DefaultPaperWidth/Height 는 제거했다(후자는 설정이 아니라
  레거시 패리티 상수다).

[산출물] 8.6MB/34파일 → 7.2MB/26파일.
- EmrDataContext 제거 — M.Framework.DBAccess/TableFramework/M.MW.Data.EMR 의 유일한 소비처였는데
  그 클래스가 어디서도 인스턴스화되지 않았다. DB 접근은 전부 raw Oracle 클라이언트를 쓴다.
- Microsoft.Web.WebView2 는 ExcludeAssets="runtime" — M.Framework.WPF 전이 의존일 뿐
  소스 참조 0건. [000]bin 과의 이름 충돌 3건도 함께 사라진다.
- Production.pubxml(win-x64, FDD, SatelliteResourceLanguages=ko). RID 를 csproj 가 아니라
  pubxml 에 둔 이유는 csproj 에 넣으면 dotnet build/test 까지 RID 별 복원을 타기 때문이다.
- tools/publish.ps1 — 비밀값 하드 게이트 + [000]bin 충돌 경고 + SHA256 매니페스트 + zip.
  게이트는 역방향으로 검증했다(appsettings.json 에 실접속 정보를 넣고 실행 → 정상 차단).
- nuget.config 신설 — 사내 피드가 개발자 개인 OneDrive 경로라 다른 머신에서 복원이 불가능했다.
  %MSYS_NUGET_FEED% 환경변수로 받게 해 최소한 실패 원인이 드러나게 했다.

[배포 규약] docs/DEPLOYMENT.md.
[000]bin 최상위 평면 복사를 금지한다 — 실측 결과 Oracle.ManagedDataAccess.dll 이 겹치고
(신규 .NET Core 5,434KB ↔ 기존 .NET FW 4,602KB), 덮으면 그 폴더의 레거시 EXE 187개가
전부 Oracle 접속 불능이 된다. [000]bin\SheetMe\ 하위 폴더에 둔다 — Information/Log/
OCR서식생성기/SpreadDesign 등 기존 앱들과 같은 방식이다.
FDD 로 배포한다: [000]bin\OCR서식생성기 가 이미 net10.0 + WindowsDesktop.App 10.0.0 을
요구하며 운영 중이라 런타임 존재가 확인된다. 없는 단말이 나오면 -SelfContained 한 번이면 된다.

[로깅] AppLog — LogManager 배선. 모든 호출을 try/catch 로 감싸 로깅 실패가 업무를 막지 않게 했다.
- Redact 필수 — 접속 문자열을 값으로 들고 다니므로 예외 메시지에 자격증명이 섞일 수 있다.
  기록 직전 1회 통과시킨다.
- LogLevel 은 열거형이 아니라 문자열 속성이라 오타를 컴파일러가 못 잡고, 잘못된 값이면
  FIXED 만 남고 나머지가 조용히 사라진다. LogType 열거값의 이름으로만 지정하게 했다.
- 문서에 있는 HandleShutdown 은 6.0.0 DLL 에 실제로는 없어(XML 문서가 앞서 있음) 쓰지 않는다.
  대신 기록이 비동기 배치라 스모크에서 짧게 폴링해 확인한다.
- 로그 경로는 실행 폴더\logs\Designer, 쓰기 불가 시 %LocalAppData% 폴백(쓰기 프로브까지 확인).

[전역 예외] Dispatcher/AppDomain/TaskScheduler 3종을 진단 분기보다 앞에 등록했다.
UI 예외는 기록 후 계속 진행한다(편집 중 문서를 예외 하나로 잃지 않게) — 단 10초 내 5회면
무한 팝업 루프이므로 강제 종료한다.
DialogService.ShowError 도입 — 우리가 던진 안내성 예외는 메시지를 그대로 보여주고, 그 외는
일반화 문구 + 오류 코드만 노출한다(코드가 로그 줄머리와 같아 전화 한 통으로 특정된다).
ex.ToString() 전문을 그대로 띄우던 2곳을 정리했다.
로그인/권한거부/DB저장은 감사 이벤트(FIXED)로 남긴다.

검증: 테스트 70/70, edit-smoke 실패 0(마스킹 5건 + 로그 배선 1건 추가), 왕복 1,271건
diff 0/예외 0, db-save-smoke 제자리 갱신 통과. publish.ps1 정방향/역방향 모두 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 19:23:27 +09:00
MsystechandClaude Fable 5 0f77cb717a HIS 사용자 컨텍스트 + 서식생성기 권한 게이트 + 서버 시각 전환
[사용자 컨텍스트] 감사 컬럼에 Windows 계정명이 들어가던 6지점을 HIS UidCod 로 교체했다.
레거시 이력 화면(ucSheetHistory.vb:51)이 SdgUidCod 를 수정자로 표시하므로 지금까지는
감사 추적이 끊겨 있었다.
- UserContextStore — M_UidMst 단독 조회로 인증 판정(레거시 bzLogIn 자동로그인과 동일 의미론,
  비밀번호를 보지 않고 행 존재 + 유효기간만 확인). 부서/병원은 LEFT JOIN best-effort 표시용.
  적용일자는 서버 시각을 쓴다 — 단말 시계가 밀리면 유효 계정이 거부될 수 있다.
- UserSession — 정적 set-once. 생성자 주입을 택하지 않은 이유는 진단 모드(--db-*)가
  ViewModel 그래프 밖이라 주입으로는 감사 기록 지점에 닿을 수 없기 때문이다. 대신 읽는 곳을
  App 초기화 / FormDesignDataBusiness / MainViewModel 표시 세 군데로 제한했고, 저장소는
  지금처럼 uid 를 파라미터로만 받는다. 진단 모드는 "SMOKE" 식별자를 쓴다.
- StartupArguments — 레거시 규약 "UidCod,ComNum,ShtCod" 관용 파싱(PadRight 후 split) 이식.
  ComNum 은 레거시에서도 선언·대입뿐인 죽은 인자라 보존만 하고 쓰지 않는다.
- App.OnStartup 재구성 — 진단 플래그 12종을 RunDiagnostic 으로 분리(각 분기가 종료 코드를
  반환하도록 정리), 세션 확정 후 MainView 표시. 기동 인자에 ShtCod 가 있으면 PendingSheetCode
  로 넘겨 빈 새 문서를 만들지 않고 그 서식만 연다(열기 실패 시에는 빈 문서로 폴백).
- 인자로 받은 UidCod 조회에 실패하면 기동을 중단한다(레거시 :892 와 동형). 인자 없이 단독
  실행하면 미인증으로 계속하되 DB 쓰기를 차단하고, 개발 편의는 His:DevUidCod 설정으로 뺐다.

[권한 게이트] E_ShtMst.ShtUsrDesYon — 'Y' 또는 미설정이면 허용, 그 외만 거부(레거시
frmSheetDesigner.vb:71-84 규칙, 거부 문구도 그대로). 레거시는 게이트가 열기 진입점마다
흩어져 있어 이력 노드 클릭·MessageQueue 두 경로로 우회가 가능했는데, SheetMe 는 모든 열기가
OpenDbSheet 로 수렴하므로 거기 한 곳이면 구조적으로 우회가 불가능하다. 추가로 이력 열람과
DB 저장에도 넣었다 — 후자는 레거시에 아예 없던 구멍이다(열기 이후 플래그가 꺼질 수 있고,
파일에서 연 문서를 DB 에 저장하는 경로는 열기 게이트를 거치지 않는다).
목록에서 거르지 않고 잠금 아이콘으로 표시한다(안 보이면 사용자가 진단할 수 없다).
--db-gate 진단 신설 — 실측 결과 차단되는 디자인 보유 서식 0건(N 은 1건뿐이고 디자인 없음).

[서버 시각] ServerClock 신설(SELECT SYSTIMESTAMP 왕복 1회로 8/12/17자리 확보, 포맷은
invariant 로 C# 에서 — NLS 의존 제거). 감사 정확성뿐 아니라 ResolveActiveSdgKey 가
ORDER BY SdgUpdDtm 으로 활성 버전을 고르기 때문에, 시계가 뒤로 밀린 단말이 저장하면
활성 버전 판정이 뒤집힐 수 있었다. Reorder 는 루프 밖 1회로 바꿔 한 번의 순서 변경이
행마다 다른 시각을 남기지 않게 했다. 실패 시 폴백하지 않는다.

RecordWordStore.Remove 에 auditUid 인자 추가 — 남길 컬럼은 없지만(런타임 조회 쿼리에
ShtStt 필터가 없어 논리 삭제로 바꾸면 지운 문구가 계속 노출된다) "모든 쓰기 API 는 uid 를
명시한다"는 규약을 컴파일 타임에 강제한다. 상용구 대화상자는 uid 를 생성자로 받고,
미인증이면 읽기 전용으로 연다.

검증: 사전 실측으로 M_UidMst/M_DepMst/M_HspMst 접근 확인(2단계 최대 리스크 해소, 유효
사용자 539명). 테스트 70/70, edit-smoke 실패 0(기동 인자 파싱 8건 추가), 왕복 1,271건
diff 0/예외 0, db-save-smoke 제자리 갱신 통과, db-word-smoke 통과.
DB 직접 확인: P163 저장분 SdgUidCod='SMOKE', SdgUpdDtm 이 서버 시각과 일치
(변경 전 저장분 S999 는 'Msystech' 로 대비됨).

남은 항목: 단일 인스턴스 + Named Pipe IPC 는 미구현. 기동 인자·게이트가 먼저 필요했고,
바로가기 운영에서는 중복 실행 억제가 덜 급하다. 다중 사용자 병행 운영 전에 처리한다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:37:46 +09:00
MsystechandClaude Fable 5 5c97bf1440 파일 계열 키바인딩 배선 + 미저장 종료 가드
메뉴에는 Ctrl+N/O/S/P 가 표시되지만 Window.InputBindings 가 아예 없어 실제로는 동작하지
않는 잘못된 어포던스였다. 그리고 MainView 에 Closing 핸들러가 없어 타이틀바 X·Alt+F4·
파일 종료 어느 경로로도 미저장 확인이 뜨지 않아, 미저장 다중 문서가 확인 없이 버려졌다.

- UndoService 에 savedDepth 기반 IsDirty/MarkSaved 추가. 기존 CanUndo 는 저장해도 영구
  더티라 종료마다 무의미한 확인이 떠서 가드가 존재 가치를 잃는다. Undo/Redo 로 저장 지점
  깊이에 되돌아오면 자동 clean 이 되고, 저장 지점보다 얕은 상태에서 새 편집이 들어오면
  (분기) 되돌아갈 수 없으므로 savedDepth=-1 로 영구 더티 처리한다. 용량 트림 시에도 보정.
- OnSaveFile/OnSaveToDb 를 bool 반환으로. 기존 void 는 저장 대화상자 취소를 감지할 수 없어
  종료 가드에서 저장 선택지를 줄 수 없었다. 성공 시 MarkSaved() 호출.
- ConfirmCloseAll() — 문서별 YesNoCancel 확인. 확인 직전 해당 문서를 활성 탭으로 올려
  어느 문서를 묻는지 보이게 한다(저장 경로가 CurrentDesigner 대상이라 전환이 필수다).
  과거 버전 열람 탭은 저장하면 활성 디자인을 과거 내용으로 덮으므로 저장 선택지를 주지 않는다.
- 종료 경로 두 개를 모두 막는다: MainView.Closing 에서 e.Cancel, 그리고 파일 종료의
  Application.Shutdown() 은 e.Cancel 을 무시하므로 VM 이 먼저 확인하고 IsShuttingDown 을
  세워 Closing 이 재질문하지 않게 한다.
- Window.InputBindings 에는 파일/앱 계열만(Ctrl+N/O/S/Shift+S/P/W). 편집 계열은
  CanvasKeyboardBehavior 가 계속 담당한다 — Ctrl+Z/C/V/X/A/D/G 를 창에 올리면 인스펙터·
  검색 상자 TextBox 의 표준 편집 키와 충돌하고 Spread 붙여넣기 가드도 우회할 여지가 생긴다.
- 저장 진입부에서 포커스가 머문 TextBox 의 BindingExpression.UpdateSource() 호출.
  인스펙터 바인딩이 UpdateSourceTrigger=LostFocus 라 값을 고치다 바로 Ctrl+S 를 누르면
  커밋되지 않은 채 저장되어 조용히 유실됐다.

검증: edit-smoke 에 더티 추적 8건 추가(초기 clean / 편집 후 dirty / MarkSaved 후 clean /
저장 지점 Undo 복귀 시 clean / 더 되돌리면 dirty / Redo 복귀 시 clean / 분기 후 영구 dirty).
1단계 전체 회귀 통과 — 테스트 70/70, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0,
db-save-smoke 제자리 갱신(P163)·버저닝(S999) 양쪽, db-word-smoke 통과.

주의: 키바인딩과 종료 대화상자의 실 UI 조작 확인은 아직 하지 않았다(무인 검증 불가 구간).
병행 운영 전 수동 확인 항목으로 남긴다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:24:10 +09:00
MsystechandClaude Fable 5 3dcac31a5b 표(Spread) 이름 변경·복제 차단 — 레거시에서 서식이 통째로 안 열리는 경로 봉쇄
Spread 격자는 E_SpdMst 에서 SpdShtCod+SpdName 으로만 조회되는데 SheetMe 에는 그 행을
만드는 경로가 없다(SELECT 3곳뿐). 이름이 바뀌거나 복제본이 생기면 조회 0건 →
Spread.vb:922-955 가 0바이트 파일을 만들고 FpSpread.Open 이 예외 →
bzDesignSheetLoader.LoadObject 에는 try/catch 가 없어 재귀를 타고 :425-427 로 전파되어
Return Nothing. 즉 표가 사라지는 수준이 아니라 그 서식이 아예 열리지 않는다.

- 이름 변경: 완전 금지(RenameControl 진입부 가드). 기존 중복 이름 거부 경로도 Rebuild() 를
  호출하도록 통일 — 지금까지는 return 만 해서 편집 상자에 잘못된 이름이 남아 있었다.
- 붙여넣기: 표만 제외하고 나머지는 통과 + 제외 건수 알림. 전체 거부는 라벨 20개에 표 1개가
  섞인 복사 같은 정상 작업까지 막는다. 조기 반환을 Undo.Snapshot() 앞에 둬 빈 스텝이
  쌓이지 않게 했다.
- 가드는 커맨드 람다가 아니라 Paste() 메서드 본문에 — CanvasKeyboardBehavior 가 커맨드를
  우회해 designer.CopySelection()/Paste() 를 직접 호출하므로 키보드 경로도 함께 막힌다.
- 잘라내기는 별도 가드 불필요(표 삭제 자체는 안전), 복제는 Copy+Paste 라 자동 차단.
- 판정은 VM 형이 아니라 모델 Type 문자열 + 자식 재귀 — 클립보드가 ControlElement 이고
  컨테이너에 중첩된 표도 잡아야 한다.

검증: edit-smoke 에 판정·필터 순수 로직 8건 추가(FilterPastable 을 알림과 분리해 무인
검증이 가능하도록 함). 실 UI 프로브로 P062(Spread 실물 보유)를 열어 실제 입력으로 이름
변경을 시도 → 가드 대화상자 발동, 레이어·인스펙터 헤더 모두 Spread1 유지, 확인 후 편집
상자 원복까지 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 17:45:53 +09:00
MsystechandClaude Fable 5 d0538ef95a 쿼리 치환 변수를 클래스 전체 이름 형식으로 교체 (치환 실패 → ORA 오류 수정)
레거시 런타임 bzDesignSheetLoader.ConvertQuery 는 토큰 접두어를
PatientInfo/SheetInfo/WorkInfo 의 GetType.FullName 과 Select Case 완전일치로 비교한다.
기존 목록은 <<PatientInfo.ChtNum>> 축약형이라 어떤 토큰도 치환되지 않고 <<...>> 가
SQL 에 리터럴로 남아 ORA 구문오류를 냈다 — 그 데이터소스를 참조하는 컨트롤이 전부 공백이 되는
조용한 실패다. 기존 서식의 쿼리는 원문 보존이라 영향 없고, 신규 작성분만 해당된다.

접두어는 ucLoadSheetBase.vb:7619-7631 이 형을 고정한다:
  PatientInfo/WorkInfo → M.CMM.HISOperatingInfo.*
  SheetInfo            → M.EMR.SheetLoadOperatingInfo.bzSheetInfo

기존 20개 중 8개는 접두어뿐 아니라 속성명 자체가 실존하지 않았다(리플렉션 null):
PatientInfo.OdrNum/OdrSeq, SheetInfo.EmrGbn/PatTyp/OdrNum/OdrSeq, WorkInfo.WrkNam/AdpDep.
bzPatientInfo/bzSheetInfo/bzWorkInfo 의 실존 공개 스칼라 속성으로 다시 큐레이션했다.
DataRow 접근형(...PatInfDR.item(컬럼))은 LastIndexOf(.) 규칙이 접두어를 깨뜨려
이 경로에서 반드시 실패하므로 목록에서 제외.

- LegacyQueryVariableCatalog 신설(그룹/설명 포함 레코드)
- 편집기 목록을 그룹 헤더 + 설명/토큰 2줄 + 툴팁으로 — 축약 오해 재발 방지
- 상용구 편집의 QueryEditorWindow 재사용에 showVariables:false 추가
  (상용구 문구 화면에 SQL 변수 목록이 노출되던 오조작 여지 제거)

검증: 테스트 70/70(레거시 완전일치 규칙 시뮬레이션 + 운영 실사용 토큰 존재 검사 포함),
edit-smoke 에 두 모드 실제 창 생성 검사 추가 — 실패 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 17:34:58 +09:00
MsystechandClaude Fable 5 c445b851a1 인스펙터 기본값 도입 + Choice 콤보의 속성 삭제 버그 수정
[값 삭제 버그] InspectorView 의 ComboBox SelectedItem 은 TwoWay 기본이라, 저장된 값이
Choices 에 없으면 null 로 코어스한 뒤 그 null 을 소스에 되써서 속성을 삭제했다.
Label TextAlign Choices 에 Bottom* 3값이 빠져 있어, 해당 라벨을 선택하는 것만으로
발동했다(운영 활성 디자인 1,305건 census: BottomCenter 126 / BottomLeft 107 / BottomRight 16).
- ContentAlignment 9값 전체로 보정
- ChoiceRowViewModel.EnsureChoice — 목록 밖 값을 표시 전에 편입(사이트 커스텀 값 방어)

[기본값] PropertyDef.Default 추가. RowBinding.Get 한 곳에서만 주입하므로
Initialize(building=true) 경로를 타 Commit 이 발생하지 않는다 — 사용자가 건드리기 전까지
PropBag 에 아무것도 쓰이지 않아 왕복 무손실이 유지된다. 다중 선택 병합도 자동으로 옳아진다.
지정 대상(레거시 필드 초기값/WinForms DefaultValueAttribute 근거):
  TextBox.RwdRsvWrdYon=True (TextBox.vb:63, 2022-09-28 추가라 이전 서식엔 키 없음)
  DataTable.ExcuteQuery=True (MDataTable.vb:15)
  Label.TextAlign=TopLeft / TextBox.TextAlign=Left / TextBox.BorderStyle=Fixed3D
  Panel.BorderStyle=None / PictureBox.SizeMode=Normal / DateTimePicker.Format=Long
이게 없으면 기본 True 속성이 항상 '꺼짐'으로 보이고, 사용자가 껐다 켜는 순간
명시 False 가 기록돼 상용구 입력·데이터소스 자동 실행이 실제로 죽는다.

ControlRegistryTests 신설 — Default⊆Choices, Toggle 기본값 표기, 그리고 운영 실사용
열거값이 Choices 에 전부 포함되는지(census 근거) 검사.

검증: 테스트 59/59, edit-smoke 실패 0, 전수 왕복 1,271건 diff 0/예외 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 17:30:28 +09:00
MsystechandClaude Fable 5 20e982faae 제자리 갱신 시 E_SctMst.SctObjTxt 갱신 누락 수정
ShtCneYon='Y' 서식은 기존 이름 행을 continue 로 건너뛰기만 해서, 라벨 문구를
고쳐도 항목사전(SctObjTxt)이 옛 문구로 남았다. 이 값은 외부인터페이스·환자전달사항·
알람이 리터럴로 매칭하는 조회 키라 조용한 데이터 불일치가 된다.

레거시 bzSaveSheetDesignNControlInfo.vb:378 과 동일하게 SctObjTxt 만 덮어쓴다:
- SctObjTyp/SctParObj/SctObjSeq/SctObjID 는 갱신하지 않음(SctKey 보존 의미론)
- 삭제된 컨트롤의 잔존 행도 지우지 않음(레거시 동일)
- SctKey 기준 UPDATE — 동명 중복 오염 데이터에서 다중 행 덮어쓰기 방지
- Spread AsTemplate 항목 행은 ObjName 이 항목 텍스트라 컨트롤 이름과 분리해 매칭.
  레거시의 매 저장 중복 INSERT 버그는 복제하지 않는다(런타임이 개수를 세지 않음).

--db-save-smoke 에 문구 변경 검증 추가 — 무변경 재저장으로는 이 경로를 증명할 수
없으므로 실제로 Text 를 바꿔 SctObjTxt 반영·SctKey 보존을 확인하고 원복한다.

검증: P163(ShtCneYon=Y) 2회 연속 통과(행 6/6 안정, SctKey 보존, 원복 확인),
S999(버저닝) diff 0 + 이전 버전 SdgDelYon='Y', 전수 왕복 1,271건 diff 0/예외 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 17:25:29 +09:00
MsystechandClaude Fable 5 16c07f48dc 초기 커밋: SheetMe 서식생성기 (P0~P5 완료 상태)
레거시 서식생성기(VB.NET WinForms) 대체용 C#/.NET 10 WPF 디자이너.
기준선: 실DB 활성 디자인 1,271건 왕복 의미론 diff 0 / 예외 0, 단위 테스트 49/49.

이 커밋에 함께 포함된 자격증명 분리:
- appsettings.json 을 __HOST__/__PASSWORD__ 플레이스홀더로 전환
- 실접속 정보는 appsettings.Development.json 으로 분리(.gitignore 제외,
  csproj Debug 조건부 복사라 Release 산출물에 실리지 않음)
- ConfigLoader 를 환경변수 > Development > appsettings 순 레이어링으로 변경,
  미치환 플레이스홀더는 '미설정'으로 간주

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 17:20:22 +09:00