워크플로 조사(읽기 5 + 반박 검증 5)로 전송 계층과 태그 17종을 훑었고,
검증이 조사서의 "FTP" 단정을 반박해 실체가 드러났다: 전송 수단은 단일
프로토콜이 아니라 DB 설정(M_DtlMst 'EMR_FileTransfer' Type)이 고르는
5갈래이고, 새 진단 --db-filecfg 로 확인한 우리 사이트 값은 Type=1 =
FileServer = ActiveX OCX(AxBITSFTLib.AxbitSFT)다. OCX 내부 프로토콜은
코드에 없고 서드파티·ActiveX 는 배포 금지라 <b>다운로드는 재현할 수 없다</b>.
그래서 받지 않고 찾는다: ①레거시 EMR 이 같은 단말에 이미 받아 둔 캐시
({설치경로}\Tmp\DownLoadedFiles\Signature\ — 레거시가 쓰는 그 폴더),
②DB 경로 자체가 열리는 경우(이 병원 저장 형태는 드라이브 절대경로).
둘 다 없으면 사유 → 종이는 빈칸(어제 결정). 판정은 순수 함수
SignatureImagePath 로 두고 테스트 7건(캐시 우선·직접 경로·둘 다 없음·
빈 경로·설치경로 없음·UNC).
경로 조회는 전부 순수 SQL 이라 그대로 이식(SignatureStore): 집도의
(DTRUIDCOD)·마취(수술 ANEUIDCOD / 부서 — 개원일 2021-03-01 분기)·
영상의학(XRAY)·진단검사(LAB)·외출외박 승인자(병동/원무)·병동 수간호사·
병원로고(M_HspMst.HspLgoPth)·병원직인(주석과 달리 DB_REGISTRY 의
'직인경로'). 로그인 사용자 3종은 세션 행에 UidImgPth 를 얹어 왕복 0,
담당의·진단서 의사는 프리페치 행 재사용.
레거시 결함 보존 2건: ETC_병리판독의사싸인은 SELECT 에 UidNam 만 있고
UidImgPth 를 읽어 항상 예외→빈 값(:17434), OCM_Cosign_싸인은 저장 기록
키(EmrKey) 의존이라 미리보기에서 값이 될 수 없다 — 둘 다 사유로 완결.
렌더: PictureBox 가 자리표시자만 그리던 것을 실제 이미지로. 파일을
잠그지 않고(OnLoad) 읽으며, 인쇄가 SizeMode 를 무시하고 사각형에 늘려
그리는 레거시 동작에 맞춰 기본 Stretch=Fill(Zoom 만 비율 유지).
WithTagValue 는 이미지류에서 Text 대신 경로 속성을 채운다.
분류도 정정 — 싸인 계열이 Image(글자만 보여 준다)에서 PatientContext 로.
- dotnet test 354/354 (경로 판정 7건 추가) · --edit-smoke 실패 0
- --db-patient ①~㊲ 전건 통과 — ㊲ 신설: 9개 경로 조회 실행,
마취과 의사 싸인 경로가 실값 29자로 나온다
- --db-filecfg 신설(값 없이 모드만): Type=1 · E_FilInf 102만행 ·
UidImgPth 157행 · 경로 모양 드라이브 절대경로
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
이름은 "부서번호"지만 값은 로그인 사용자 부서의 전화번호다
(bzDataInterface.vb:17287-17313 — M_DepMst.DepTelNum, SYSDATE 기준
M_UidMst 조인). 우리 로그인 조회가 이미 같은 시점·같은 조인으로
부서 행을 붙이고 있어 컬럼 하나(NVL(d.DepTelNum,' '))만 얹었다 —
왕복이 늘지 않는다. HisUser.DepPhone → UserField.DepPhone.
- dotnet test 338/338 · --edit-smoke 실패 0
- --db-patient ①~㉞ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## 세션 조회 확장(UserContextStore)
M_UidMst 행에서 UidLicNum·UidSpcLic 를 더 읽고, 직종명은 레거시 그대로
DtsDtlCod='JOBCOD' 만으로 M_DtsMst 를 조인해(bzDataInterface.vb:17944 —
DtsTblCod 조건이 없다) DtsCodNam 을 가져온다. HisUser 에 세 필드 추가.
## 태그 5종 + Dead 1종
- ETC_로그인_의사면허번호(UidLicNum) · 전문의번호(UidSpcLic) · 직종(DtsCodNam)
- ETC_로그인_근무부서 = 세션 부서 한글명. 레거시는 개원일(HspStrDte>=20220701)로
DepOfcNam 을 가르지만 개원일 미상 결정에 따라 DepKorNam 갈래다(기존 결정과 동일).
- ETC_로그인_근무부서_사용자명 = "부서-이름". 레거시는 UidNam 으로 걸러
동명이인에서 남의 부서가 나올 수 있는데 세션 사용자 행이라 결정적이다(주석 기록).
- ETC_로그인_직급 → Dead. 값은 UidNam 을 담고 조건은 UidCod 로 걸어(:17262·17272)
실무상 늘 빈칸이던 태그다. 고치면 없던 값이 갑자기 채워지므로 합의 전까지 레거시 유지.
레거시의 면허·전문의번호 조회는 유효기간 없이 M_DepMst 를 불필요하게 조인해
ORDER BY 없이 첫 행을 집는 비결정 조회였다(조사 문서 risk) — 세션 행(유효기간 검증
완료)에서 읽는 쪽이 결정적이고 값 원천(M_UidMst 같은 행)은 같다.
## 게이트
- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 100종)
- --db-patient 전건 통과 · --db-render P062 md5 불변
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## conflict 6건 (통합 계획의 "고칠 것")
값이 바뀌는 것 둘:
- ETC_현재년도 — "2026" → "2026년"(bzDataInterface.vb:16923 의 + "년").
형제 ETC_현재일시_1_년 은 접미가 없어 둘을 갈랐다(ClockFormat.YearSuffixed 신설).
"2026" 을 단정하던 테스트도 고쳤다.
- ETC_로그인_사용자ID_사용자명 — 레거시(:17930)는 빈 값 검사 없이 항상 결합한다.
한쪽이 비어도 "U01." 이 그대로 나간다. 조건을 붙였던 것이 레거시와 달랐다.
행 선택이 바뀌는 것 둘:
- ETC_로그인_사용자연락처 / 원내연락처 — 전에는 M_UidMst 를 조건 없이 첫 행.
이력이 여러 행인 사용자에서 옛 연락처가 나올 수 있었다.
유효기간(SYSDATE 일자 BETWEEN)으로 고르고, 0행이면 레거시 폴백(:10641)처럼
조건 없이 다시 찾는다.
의도적 차이로 기록한 것 둘(코드 주석):
- 유효기간 판정 일자 — 레거시는 로그인 시점, 여기는 SYSDATE(자정 넘긴 세션에서만 갈림).
- Trim — 레거시는 무가공, 여기는 걷는다(CHAR 잔여 공백이 종이에 찍히는 쪽이 더 이상하다).
## new 48종 중 재료 재사용으로 되는 10종
워크플로 매핑 상세(docs/new48-mappings.json)를 붙여 보니 46종이 needs-lookup 인데,
그중 10종은 <b>이미 든 행의 다른 컬럼</b>이거나 문맥 계산이었다:
- 담당의 행 재사용 3: OCM_담당의사_영문(UidEngNam) · 면허번호(UidLicNum) · 전문의번호(UidSpcLic)
— 조회 갈래가 OCM_담당의사와 문자 그대로 같고 집는 컬럼만 다르다(:4094·4119·4144)
- CowInf 재사용 3: OCM_병실베드(CowBedCod) · 병동_병실 · 병동_병실_베드(" / " 결합, 행 없으면 "")
- 문맥 계산 1: OCM_내원유형(입원/응급실/외래 — ComPatTyp·ComEmgYon)
- User 계열 3: OCM_User진료과 · _한글명칭 · User진료의사명.
로그인 사용자가 의사(UidDtrYon=Y)면 사용자의 과·이름, 아니면 환자의 것.
HisUser 에 DoctorYn·DepOfficialName 을 추가했다(세션 조회 SELECT 확장).
남은 new 는 새 조회가 필요한 세 무리다: 상병(Okd_상병쿼리) · 수술(GetOprInfDT) ·
심사(Bkd_상병쿼리). 다음 배치.
## 게이트
- dotnet test 338/338 (현재년도·ID.사용자명 판정 갱신)
- --edit-smoke 실패 0 (이름 검사 40종)
- --db-patient ①~㉔ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
[사용자 컨텍스트] 감사 컬럼에 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>