Commit Graph
56 Commits
Author SHA1 Message Date
MsystechandClaude Fable 5 7591fbed74 [200] 디자인 동일화 1/2 — 스크롤바·창 기본값·아이콘 색 인자
[200]SheetMe Studio Designer 와 6개 표면을 대조한 결과, 뼈대는 이미 같았다 —
WindowChrome 파라미터, 5열 골격, 색 토큰 다크·라이트 64개 값 전부, Button/TextBox/Menu/
TabItem/ComboBox/CheckBox 등 스타일이 바이트 단위로 일치한다. 실제 격차는 "껍데기 안에
무엇이 들어가는가" 셋이었다: 스크롤바, 타이틀바 구성, 좌측 패널·인스펙터 정보 밀도.
이번 커밋은 그중 전 화면에 깔려 체감이 가장 큰 스크롤바와 테마 기반을 맞춘다.

[스크롤바] [200] 통일 규격 이식 — 9px 폭(기존 11px), 상하 14px 화살표 버튼, 막대 CornerRadius 5.
막대 색은 토큰이 아니라 리터럴 중립 그레이(#8A8F96/#A6ACB3/#C4CAD0)다 — 다크·라이트 양쪽에서
같은 회색으로 보여야 배경에 따라 튀지 않는다([200] 과 동일한 의도적 선택).
MinThumbTrack.cs 를 [200] src/Ui/SheetMe.Ui 에서 복사 이식(vendoring, 헤더에 동기화 의무 명시).
기본 WPF Track 은 Thumb.MinHeight 를 무시해 항목이 많은 목록에서 막대가 실처럼 가늘어지는데,
기존 ScrollThumb 의 MinHeight 48 이 바로 그래서 먹지 않고 있었다. MinThumbTrack 이 arrange 를
직접 해 최소 길이 44px 를 보장한다.

[창 기본값] Window 암시 스타일에 FontSize 13 + Foreground B.Ink 추가, Background 를
B.Panel → B.AppBg 로. 이게 없어 대화상자 8종이 WPF 기본 12px + 시스템 검정으로 렌더돼
메인 셸(13px)과 결이 어긋나 있었다. 토큰 참조는 DynamicResource 유지 — [200] 의
StaticResource 를 그대로 베끼면 다크에서 값이 굳는다.

[아이콘 색 인자] 컨버터 ConverterParameter 를 "크기" 단독에서 "크기|브러시키" 로 확장.
구분자가 없으면 종전 동작(크기만, B.Muted)을 그대로 유지해 기존 호출부 7곳이 안전하다.
지금까지 B.Muted 가 하드코딩돼 있어 활성 문서 탭 아이콘 강조나 레이어 타입 아이콘 색 구분이
구조적으로 불가능했다. 후속 배치의 선행 조건이다.
팔레트 아이콘 매핑도 정리 — MaskedTextBox 가 TextBox 와 같은 아이콘이라 구분이 안 됐다.

[그 외] DocTabItem 비활성 탭에 하단 테두리를 넣고 활성만 끊어 크롬식 룩으로.
BarToolToggle(플로팅 바 선택/손 — 켜짐을 B.Accent2 로 은은하게) 과 MiniToggle 키 스타일 신설.
암시 ToggleButton 스타일은 인스펙터·레이어 토글도 쓰므로 손대지 않고 키로 분리했다.
B.FloatShadow 토큰 신설 — 플로팅 바 그림자가 고정값 하나라 라이트에서 흰 배경에 검은 그림자가
그대로 얹혀 있었다. 다크 20/3/0.5, 라이트 26/5/0.14 로 분리. 기존 34개 토큰 값은 손대지 않았다.

검증: 테스트 124/124, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0.
**종이 렌더 P062 픽셀 대조 차이 0** — 암시 스타일 변경이 용지에 새지 않음을 확인.
x:Key 없는 암시 스타일 개수도 이식 전과 동일(25개)하게 유지했다.

남은 배치: C(인스펙터 접이식 섹션·세그 토글) · D(좌측 패널·타이틀바·빈 상태) ·
E(선택 오버레이) · F(캔버스 크롬 — 종이에 닿는 유일한 배치) · G(토스트·ThemedDialog).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 10:10:06 +09:00
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 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 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 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