워크플로 조사(읽기 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>
120 lines
6.2 KiB
C#
120 lines
6.2 KiB
C#
using Oracle.ManagedDataAccess.Client;
|
|
|
|
namespace SheetMe.Data.Stores;
|
|
|
|
/// <summary>
|
|
/// HIS 로그인 사용자 — 감사 컬럼(SdgUidCod/ShtUpdUid 등)에 기록되는 UidCod 의 유일한 출처.
|
|
/// 부서·병원은 표시 전용이며 없어도 인증은 성립한다(레거시 bzLogIn 과 동일 의미론).
|
|
/// </summary>
|
|
public sealed record HisUser(
|
|
string UidCod, string UidNam,
|
|
string DepCod, string DepNam,
|
|
string HspCod, string HspNam,
|
|
/// <summary>의사 여부(UidDtrYon=Y) — OCM_User진료과 계열이 로그인 사용자 분기에 쓴다</summary>
|
|
string DoctorYn = "",
|
|
/// <summary>부서 공식 명칭(DepOfcNam) — 개원일 조건이 걸리는 병원에서 한글명 대신 쓴다</summary>
|
|
string DepOfficialName = "",
|
|
/// <summary>의사면허번호(UidLicNum) — ETC_로그인_의사면허번호</summary>
|
|
string LicenseNumber = "",
|
|
/// <summary>전문의번호(UidSpcLic) — ETC_로그인_전문의번호</summary>
|
|
string SpecialistNumber = "",
|
|
/// <summary>직종명(M_DtsMst JOBCOD 의 DtsCodNam) — ETC_로그인_직종</summary>
|
|
string JobName = "",
|
|
/// <summary>부서 전화번호(M_DepMst.DepTelNum) — ETC_접속유저_부서번호(이름과 달리 전화번호다)</summary>
|
|
string DepPhone = "",
|
|
/// <summary>영문 이름(UidEngNam) — 진단서의사_영문의 로그인 의사 갈래</summary>
|
|
string EnglishName = "",
|
|
/// <summary>세부전문의번호(UidDtlSpcLic) — 진단서의사세부전문의번호</summary>
|
|
string DetailSpecialistNumber = "",
|
|
/// <summary>싸인 이미지 원격 경로(UidImgPth) — 로그인 사용자 싸인 계열</summary>
|
|
string SignaturePath = "")
|
|
{
|
|
/// <summary>미인증 세션(인자 없이 단독 실행) — DB 쓰기가 차단된다</summary>
|
|
public static readonly HisUser Unknown = new(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty);
|
|
|
|
/// <summary>HIS 사용자로 확인되었는지</summary>
|
|
public bool IsAuthenticated => UidCod.Length > 0;
|
|
|
|
/// <summary>표시용 — "홍길동(MSYS)" 형태, 미인증이면 안내 문구</summary>
|
|
public string Display => IsAuthenticated
|
|
? (UidNam.Length > 0 ? $"{UidNam}({UidCod})" : UidCod)
|
|
: "사용자 미확인";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 사용자 컨텍스트 조회 — 레거시 bzLogIn.LogIn(uid, autoLogin) 의 신규 대응물.
|
|
/// 레거시도 자동 로그인에서는 비밀번호를 확인하지 않고 M_UidMst 행 존재 + 유효기간만 판정한다
|
|
/// (daLogIn.vb: SELECT * FROM M_UidMst WHERE UidCod=:u AND :adp BETWEEN UidStrDte AND UidEndDte).
|
|
/// </summary>
|
|
public sealed class UserContextStore
|
|
{
|
|
#region Member Fields
|
|
private readonly string connectionString;
|
|
#endregion
|
|
|
|
#region Constructors
|
|
public UserContextStore(string connectionString)
|
|
{
|
|
this.connectionString = connectionString;
|
|
}
|
|
#endregion
|
|
|
|
#region Methods
|
|
/// <summary>
|
|
/// UidCod 검증 + 표시 정보 조회 — 미존재이거나 유효기간 밖이면 null.
|
|
/// 적용일자는 서버 시각을 쓴다(단말 시계가 밀리면 유효 계정이 거부될 수 있다).
|
|
/// 부서·병원은 LEFT JOIN 이라 마스터에 행이 없어도 인증 자체는 성립한다.
|
|
/// </summary>
|
|
public HisUser? Resolve(string uidCod)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(uidCod))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using var connection = new OracleConnection(connectionString);
|
|
connection.Open();
|
|
using var command = connection.CreateCommand();
|
|
command.BindByName = true;
|
|
// 레거시와 두 가지가 <b>의도적으로</b> 다르다(워크플로 검증이 짚었고, 받아들이기로 했다):
|
|
// · 유효기간 판정 일자 — 레거시는 로그인 시점(GetAdpDtm 앞 8자, clsCommonLib.vb:10622),
|
|
// 여기는 SYSDATE. 자정을 넘겨 계속 켜 둔 세션에서만 갈린다.
|
|
// · Trim — 레거시는 UidCod/UidNam 을 무가공으로 쓰고(bzDataInterface.vb:16713, 16720)
|
|
// 여기는 걷는다. CHAR 잔여 공백이 종이에 찍히는 쪽이 더 이상하다고 판단했다.
|
|
command.CommandText =
|
|
"SELECT u.UidCod, NVL(u.UidNam,' '), " +
|
|
" NVL(u.UidDepCod,' '), NVL(d.DepKorNam,' '), " +
|
|
" NVL(u.UidHspCod,' '), NVL(h.HspInsNam,' '), " +
|
|
" NVL(u.UidDtrYon,' '), NVL(d.DepOfcNam,' '), " +
|
|
" NVL(u.UidLicNum,' '), NVL(u.UidSpcLic,' '), NVL(j.DtsCodNam,' '), " +
|
|
" NVL(d.DepTelNum,' '), NVL(u.UidEngNam,' '), NVL(u.UidDtlSpcLic,' '), " +
|
|
" NVL(u.UidImgPth,' ') " +
|
|
"FROM M_UidMst u " +
|
|
"LEFT JOIN M_DepMst d ON d.DepCod = u.UidDepCod " +
|
|
" AND TO_CHAR(SYSDATE,'YYYYMMDD') BETWEEN d.DepStrDte AND d.DepEndDte " +
|
|
"LEFT JOIN M_HspMst h ON h.HspCod = u.UidHspCod " +
|
|
" AND TO_CHAR(SYSDATE,'YYYYMMDD') BETWEEN h.HspStrDte AND NVL(h.HspEndDte,'99991231') " +
|
|
// 직종 조인은 레거시 그대로 DtsDtlCod 만으로 건다(bzDataInterface.vb:17944 — DtsTblCod 조건이 없다)
|
|
"LEFT JOIN M_DtsMst j ON j.DtsDtlCod = 'JOBCOD' AND j.DtsCod = u.UidJobCod " +
|
|
" AND TO_CHAR(SYSDATE,'YYYYMMDD') BETWEEN j.DtsStrDte AND j.DtsExpDte " +
|
|
"WHERE u.UidCod = :u " +
|
|
"AND TO_CHAR(SYSDATE,'YYYYMMDD') BETWEEN u.UidStrDte AND u.UidEndDte";
|
|
command.Parameters.Add(new OracleParameter("u", uidCod.Trim()));
|
|
|
|
using var reader = command.ExecuteReader();
|
|
if (!reader.Read())
|
|
{
|
|
return null;
|
|
}
|
|
return new HisUser(
|
|
reader.GetString(0).Trim(), reader.GetString(1).Trim(),
|
|
reader.GetString(2).Trim(), reader.GetString(3).Trim(),
|
|
reader.GetString(4).Trim(), reader.GetString(5).Trim(),
|
|
reader.GetString(6).Trim(), reader.GetString(7).Trim(),
|
|
reader.GetString(8).Trim(), reader.GetString(9).Trim(), reader.GetString(10).Trim(),
|
|
reader.GetString(11).Trim(), reader.GetString(12).Trim(), reader.GetString(13).Trim(),
|
|
reader.GetString(14).Trim());
|
|
}
|
|
#endregion
|
|
}
|