싸인·직인·로고 17종 — 경로 조회와 이미지 렌더 이식(전송 계층은 재현 불가)
워크플로 조사(읽기 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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
2310971a10
commit
a0333791b6
@@ -0,0 +1,158 @@
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
/// <summary>
|
||||
/// 싸인·직인·로고 태그가 쓰는 <b>원격 이미지 경로</b> 조회 — 레거시 bzDataInterface.vb 의
|
||||
/// 싸인 태그 17종에서 기계 추출했다. 태그 본문은 전부 같은 골격이다:
|
||||
/// 경로를 DB 에서 한 칸 얻고 → 파일서버에서 내려받아 → 로컬 경로를 돌려준다.
|
||||
///
|
||||
/// <b>여기서는 경로만 얻는다.</b> 내려받는 계층(clsFileTransfer)은 이 병원 설정에서
|
||||
/// ActiveX OCX(M_DtlMst 'EMR_FileTransfer' Type=1)라 재현할 수 없다 — 그 판정과
|
||||
/// 대안(레거시 캐시 사용)은 <see cref="Core.Catalog.SignatureImagePath"/> 에 있다.
|
||||
///
|
||||
/// 대부분의 원문이 ORDER BY 없는 Rows(0) 이라 정렬을 명시해 한 행으로 고정했다
|
||||
/// (담당의 때 정한 규칙). 각 메서드 주석에 근거 줄번호를 남긴다.
|
||||
/// </summary>
|
||||
public sealed class SignatureStore
|
||||
{
|
||||
#region Member Fields
|
||||
private readonly string connectionString;
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public SignatureStore(string connectionString)
|
||||
{
|
||||
this.connectionString = connectionString;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>
|
||||
/// 수술 집도의 싸인(OCM_수술집도의_싸인, :3385) — 최신 수술(OprKey DESC)의 집도의
|
||||
/// (S_OprTyp 'DTRUIDCOD')를 오늘 기준 사용자·부서 마스터로 풀어 UidImgPth.
|
||||
/// 원문의 OprStt 무필터·SYSDATE 유효기간을 그대로 옮긴다.
|
||||
/// </summary>
|
||||
public string SurgeonSignature(decimal comNum)
|
||||
=> Scalar(
|
||||
"SELECT v FROM (SELECT UidImgPth v FROM S_OprInf"
|
||||
+ " INNER JOIN S_OprTyp ON OprKey = OprKeyNum AND OprComNum = :m AND OprTyp = 'DTRUIDCOD'"
|
||||
+ " INNER JOIN M_UidMst ON UidCod = OprCod"
|
||||
+ " AND TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN UidStrDte AND UidEndDte"
|
||||
+ " INNER JOIN M_DepMst ON DepCod = UidDepCod"
|
||||
+ " AND TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN DepStrDte AND DepEndDte"
|
||||
+ " ORDER BY OprKey DESC) WHERE ROWNUM = 1",
|
||||
("m", comNum));
|
||||
|
||||
/// <summary>
|
||||
/// 수술 마취의사 싸인(OCM_수술마취의사싸인_수술접수연동, :3235) — 같은 골격에
|
||||
/// 오더 타입만 'ANEUIDCOD'(부서 조인 없음).
|
||||
/// </summary>
|
||||
public string AnesthesiaSignatureBySurgery(decimal comNum)
|
||||
=> Scalar(
|
||||
"SELECT v FROM (SELECT UidImgPth v FROM S_OprInf"
|
||||
+ " INNER JOIN S_OprTyp ON OprKey = OprKeyNum AND OprComNum = :m AND OprTyp = 'ANEUIDCOD'"
|
||||
+ " INNER JOIN M_UidMst ON UidCod = OprCod"
|
||||
+ " AND TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN UidStrDte AND UidEndDte"
|
||||
+ " ORDER BY OprKey DESC) WHERE ROWNUM = 1",
|
||||
("m", comNum));
|
||||
|
||||
/// <summary>
|
||||
/// 마취과 의사 싸인(OCM_수술마취의사싸인, :3311) — 환자와 무관하게 <b>마취과 소속</b>
|
||||
/// 재직자 하나. 개원일 2021-03-01 이후 병원은 청구·그룹 코드로, 그 전은 부서명 LIKE
|
||||
/// '%마취%' 로 찾는다(원문 주석: "코드로 하기엔 병원마다 다를거같아서").
|
||||
/// 원문에 ORDER BY 가 없어 임의 행이었다 — 사용자 코드 순 첫 행으로 고정한다.
|
||||
/// </summary>
|
||||
public string AnesthesiaDepartmentSignature(bool byCode)
|
||||
=> Scalar(
|
||||
"SELECT v FROM (SELECT UidImgPth v FROM M_UidMst"
|
||||
+ " INNER JOIN M_DepMst ON DepCod = UidDepCod"
|
||||
+ " AND TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN DepStrDte AND DepEndDte"
|
||||
+ " WHERE TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN UidStrDte AND UidEndDte"
|
||||
+ (byCode
|
||||
? " AND DepBilCod = '09' AND DepGrpCod = 'ANS'"
|
||||
: " AND DepKorNam LIKE '%마취%'")
|
||||
+ " ORDER BY UidCod) WHERE ROWNUM = 1");
|
||||
|
||||
/// <summary>
|
||||
/// 부서 코드로 찾는 의사 싸인 — 영상의학과(ETC_영상의학과의사싸인 :17353, 'XRAY')와
|
||||
/// 진단검사(ETC_진단검사의사싸인 :17578) 계열의 공통형. 기준일은 작업일(없으면 오늘).
|
||||
/// 원문 ORDER BY 없음 → 사용자 코드 순 고정.
|
||||
/// </summary>
|
||||
public string DepartmentDoctorSignature(string depCod, string day8)
|
||||
=> depCod.Trim().Length == 0 ? string.Empty
|
||||
: Scalar(
|
||||
"SELECT v FROM (SELECT UidImgPth v FROM M_UidMst"
|
||||
+ " WHERE UidDtrYon = 'Y' AND UidDepCod = :d"
|
||||
+ " AND :y BETWEEN UidStrDte AND UidEndDte"
|
||||
+ " ORDER BY UidCod) WHERE ROWNUM = 1",
|
||||
("d", depCod.Trim()), ("y", day8));
|
||||
|
||||
/// <summary>
|
||||
/// 외출·외박 승인자 싸인(ETC_외출외박_확인간호사싸인 :17683 / _확인원무과싸인 :17765) —
|
||||
/// 신청 행의 승인자 코드(병동 OutAdmWrdAppUid / 원무 OutAdmOffAppUid)를 오늘 기준
|
||||
/// 사용자 마스터로 푼다. 원문 두 조회 모두 ORDER BY 없음 → 신청 시작일 최신으로 고정.
|
||||
/// </summary>
|
||||
public string OutingApproverSignature(decimal comNum, bool officeApprover)
|
||||
=> Scalar(
|
||||
"SELECT v FROM (SELECT UidImgPth v FROM O_OutAdmInf"
|
||||
+ " INNER JOIN M_UidMst ON UidCod = "
|
||||
+ (officeApprover ? "OutAdmOffAppUid" : "OutAdmWrdAppUid")
|
||||
+ " AND TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN UidStrDte AND UidEndDte"
|
||||
+ " WHERE OutAdmComNum = :m"
|
||||
+ " ORDER BY NVL(OutAdmStrDtm, OutAdmExpStrDtm) DESC) WHERE ROWNUM = 1",
|
||||
("m", comNum));
|
||||
|
||||
/// <summary>
|
||||
/// 병동 수간호사 싸인(ETC_로그인사용자_병동_수간호사_싸인, :15562) — 이름 태그와 같은
|
||||
/// 조회의 이미지 컬럼. 원문은 부서 코드를 문자열 연결로 넣는다 — 바인드로 정상화.
|
||||
/// </summary>
|
||||
public string WardHeadNurseSignature(string depCod)
|
||||
=> depCod.Trim().Length == 0 ? string.Empty
|
||||
: Scalar(
|
||||
"SELECT v FROM (SELECT UidImgPth v FROM M_UidMst"
|
||||
+ " WHERE TRIM(UidDepCod) = :d"
|
||||
+ " AND UidPstCod IN (SELECT c FROM (SELECT DtsCod c FROM M_DtsMst"
|
||||
+ " WHERE TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN DtsStrDte AND DtsExpDte"
|
||||
+ " AND DtsDtlCod = 'PSTCOD' AND DtsCodNam IN ('수간호사','간호수선생')"
|
||||
+ " ORDER BY DtsCod) WHERE ROWNUM = 1)"
|
||||
+ " AND TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN UidStrDte AND UidEndDte"
|
||||
+ " ORDER BY UidCod) WHERE ROWNUM = 1",
|
||||
("d", depCod.Trim()));
|
||||
|
||||
/// <summary>
|
||||
/// 병원 로고(ETC_요양기관명칭_병원로고, :16538) — M_HspMst.HspLgoPth, 무기한 유효
|
||||
/// (HspEndDte='29991231') 행. 원문은 0행이면 Rows(0) 예외였다 — 여기는 빈 값.
|
||||
/// </summary>
|
||||
public string HospitalLogo()
|
||||
=> Scalar(
|
||||
"SELECT v FROM (SELECT HspLgoPth v FROM M_HspMst"
|
||||
+ " WHERE HspEndDte = '29991231' ORDER BY HspCod) WHERE ROWNUM = 1");
|
||||
|
||||
/// <summary>
|
||||
/// 병원 직인(ETC_요양기관명칭_병원직인, :16580) — <b>병원 마스터가 아니라</b>
|
||||
/// 설정 표(DB_REGISTRY, MnuNam='직인경로')의 SelYon 이다(원문에서 M_HspMst.HspStpPth
|
||||
/// 조회는 주석 처리돼 있다). SCHILDREN·HSYUIL·JAH 는 즉시 빈 값 — 미이식 병원이다.
|
||||
/// </summary>
|
||||
public string HospitalStamp()
|
||||
=> Scalar(
|
||||
"SELECT v FROM (SELECT SelYon v FROM DB_REGISTRY"
|
||||
+ " WHERE MnuNam = '직인경로' ORDER BY UidCod) WHERE ROWNUM = 1");
|
||||
|
||||
/// <summary>스칼라 1칸 — 0행이거나 NULL 이면 빈 문자열</summary>
|
||||
private string Scalar(string sql, params (string Name, object Value)[] binds)
|
||||
{
|
||||
using var connection = new OracleConnection(connectionString);
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
command.CommandText = sql;
|
||||
foreach (var (name, value) in binds)
|
||||
{
|
||||
command.Parameters.Add(new OracleParameter(name, value));
|
||||
}
|
||||
var result = command.ExecuteScalar();
|
||||
return result is null or DBNull ? string.Empty : result.ToString()!.Trim();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -25,7 +25,9 @@ public sealed record HisUser(
|
||||
/// <summary>영문 이름(UidEngNam) — 진단서의사_영문의 로그인 의사 갈래</summary>
|
||||
string EnglishName = "",
|
||||
/// <summary>세부전문의번호(UidDtlSpcLic) — 진단서의사세부전문의번호</summary>
|
||||
string DetailSpecialistNumber = "")
|
||||
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);
|
||||
@@ -85,7 +87,8 @@ public sealed class UserContextStore
|
||||
" 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(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 " +
|
||||
@@ -109,7 +112,8 @@ public sealed class UserContextStore
|
||||
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(11).Trim(), reader.GetString(12).Trim(), reader.GetString(13).Trim(),
|
||||
reader.GetString(14).Trim());
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user