From 3e9d5b5cf372af071263668ddc5db99c6c10bd34 Mon Sep 17 00:00:00 2001 From: Msystech Date: Wed, 19 Aug 2026 15:51:29 +0900 Subject: [PATCH] =?UTF-8?q?STFDTR(=EC=A3=BC=EC=B9=98=EC=9D=98)=EC=9D=84=20?= =?UTF-8?q?=EC=98=AE=EA=B8=B0=EA=B3=A0=20=5FRefer=20=EA=B3=84=EC=97=B4?= =?UTF-8?q?=EC=9D=98=20=EC=A7=84=EC=8B=A4=EC=9D=84=20=EB=B0=9D=ED=9E=8C?= =?UTF-8?q?=EB=8B=A4=20=E2=80=94=2075=EC=A2=85=20=E2=86=92=2076=EC=A2=85?= =?UTF-8?q?=20+=20=EC=82=AC=EC=9C=A0=20=EC=A0=95=EC=A0=95=20~40=EC=A2=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## OCM_STFDTR 담당의 조회를 코드 컬럼만 일반화했다(CodDtrCod → CodStfDtr 파라미터). 시각 갈래는 담당의와 같다(bzDataInterface.vb:4026-4067). 코드가 비어 있으면 레거시도 즉시 "" — 진료과 행이 돌아오면 주치의 행이 아니므로 비운다. ## _Refer 계열은 "안 옮긴 것"이 아니었다 참조 내원은 EMR 실행 화면(ActionTag — fmCodLstActionTag.vb:54 등)에서 사용자가 지정하는 값이고, 레거시 서식생성기 미리보기(TestPatientSetting)는 그것을 설정하지 않는다. 즉 레거시 미리보기에서도 _Refer 태그는 전부 빈 값이다. 지금까지 그 태그들이 "환자 태그이지만 아직 옮기지 않았습니다"로 나왔는데 그건 틀린 사유다 — 옮길 것이 없는 게 아니라, 이 화면에는 참조 내원이라는 개념 자체가 없다. 사유를 갈랐다: "참조 내원 태그입니다 — 참조 내원은 EMR 실행 화면에서 지정되며, 레거시 미리보기에서도 빈 값입니다". _Refer ~40종의 표시가 이것으로 바뀐다. 이로써 서식생성기 미리보기 기준의 레거시 호환에서 _Refer 계열은 완결이다 — 레거시가 빈 값인 자리는 빈 값(+정확한 사유)이 맞는 이식이다. ## 게이트 - dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 76종) - --db-patient 전건 통과 · --db-render P062 md5 불변 Co-Authored-By: Claude Opus 5 --- .../Stores/PatientContextStore.cs | 6 ++-- .../Services/PatientIdentity.cs | 33 +++++++++++++++++++ .../Services/PatientSession.cs | 3 +- .../Services/PatientTagResolver.cs | 30 +++++++++++++++-- .../Views/PreviewWindow.xaml.cs | 3 +- 5 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/SheetMe.Data/Stores/PatientContextStore.cs b/src/SheetMe.Data/Stores/PatientContextStore.cs index 6ce51d3..759772e 100644 --- a/src/SheetMe.Data/Stores/PatientContextStore.cs +++ b/src/SheetMe.Data/Stores/PatientContextStore.cs @@ -145,7 +145,9 @@ public sealed class PatientContextStore /// /// 진료과 행을 고를 적용일시 12자리 /// 담당의 유효기간을 볼 날짜 8자리. 비면 의 앞 8자리 - public IReadOnlyDictionary Doctor(decimal comNum, string moment, string? doctorDay = null) + /// 진료과 행에서 의사 코드를 집을 컬럼 — 담당의 CodDtrCod, 주치의(전공의) CodStfDtr + public IReadOnlyDictionary Doctor(decimal comNum, string moment, + string? doctorDay = null, string codeColumn = "CodDtrCod") { using var connection = new OracleConnection(connectionString); connection.Open(); @@ -154,7 +156,7 @@ public sealed class PatientContextStore "SELECT * FROM (SELECT * FROM P_CodInf WHERE CodComNum = :k AND CodMtiSeq = 0" + " AND :t BETWEEN CodStrDtm AND CodEndDtm ORDER BY CodStrDtm DESC) WHERE ROWNUM = 1", ("k", comNum), ("t", moment)); - var code = PatientContext.Value(cod, "CodDtrCod"); + var code = PatientContext.Value(cod, codeColumn); if (code.Length == 0) { return cod; diff --git a/src/SheetMe.Designer/Services/PatientIdentity.cs b/src/SheetMe.Designer/Services/PatientIdentity.cs index c244d13..635a80a 100644 --- a/src/SheetMe.Designer/Services/PatientIdentity.cs +++ b/src/SheetMe.Designer/Services/PatientIdentity.cs @@ -94,6 +94,39 @@ public static class PatientIdentity } } + /// + /// 주치의(전공의) 행 — 진료과 행의 CodStfDtr 로 찾는다. 시각 갈래는 담당의와 같다 + /// (bzDataInterface.vb:4026-4067). 코드가 비어 있으면 빈 사전 — 레거시도 즉시 "" 다. + /// + public static IReadOnlyDictionary StaffDoctorOf(PatientContext context) + { + var connection = ConfigService.Current.ConnectionString; + var empty = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (connection.Length == 0) + { + return empty; + } + var inpatient = PatientContext.Value(context.ComInf, "ComPatTyp") == "I"; + var accepted = PatientContext.Value(context.ComInf, "ComAcpDtm"); + var left = PatientContext.Value(context.ComInf, "ComLevDtm"); + var discharged = PatientContext.Value(context.ComInf, "ComPrgStt") == "ILV"; + var moment = inpatient + ? (discharged && left.Length >= 12 ? left[..12] : context.AdpDtm) + : (accepted.Length >= 12 ? accepted[..12] : context.AdpDtm); + var doctorDay = inpatient ? null : (accepted.Length >= 8 ? accepted[..8] : null); + try + { + var row = new PatientContextStore(connection) + .Doctor(context.ComNum, moment, doctorDay, "CodStfDtr"); + // 코드가 비어 있으면 Doctor 가 진료과 행을 돌려준다 — 주치의 행이 아니므로 비운다 + return row.ContainsKey("UidNam") ? row : empty; + } + catch + { + return empty; + } + } + /// /// 상병 행 공급자 — 해석기가 필요할 때 갈래(전체/주/부)별로 부른다. /// 여기서 미리 읽지 않는 이유는 상병 태그가 없는 서식이 다수이기 때문이다. diff --git a/src/SheetMe.Designer/Services/PatientSession.cs b/src/SheetMe.Designer/Services/PatientSession.cs index 1aaa25b..1b43ad5 100644 --- a/src/SheetMe.Designer/Services/PatientSession.cs +++ b/src/SheetMe.Designer/Services/PatientSession.cs @@ -66,7 +66,8 @@ public static class PatientSession SheetMe.Core.Catalog.ITagValueResolver tags = Current is { } picked ? new PatientTagResolver(picked, session, PatientIdentity.ResidentNumberOf(picked), PatientIdentity.AddressOf(picked), - PatientIdentity.DoctorOf(picked), PatientIdentity.DepartmentOf(picked), + PatientIdentity.DoctorOf(picked), PatientIdentity.StaffDoctorOf(picked), + PatientIdentity.DepartmentOf(picked), PatientIdentity.HospitalRowOf(picked), PatientIdentity.DiagnosisSourceFor(picked), PatientIdentity.SurgerySourceFor(picked), diff --git a/src/SheetMe.Designer/Services/PatientTagResolver.cs b/src/SheetMe.Designer/Services/PatientTagResolver.cs index d053007..3c506ee 100644 --- a/src/SheetMe.Designer/Services/PatientTagResolver.cs +++ b/src/SheetMe.Designer/Services/PatientTagResolver.cs @@ -157,6 +157,9 @@ public sealed class PatientTagResolver : ITagValueResolver /// 이 내원의 담당의 행(M_UidMst) — 없으면 빈 사전 private readonly IReadOnlyDictionary doctor; + /// 이 내원의 주치의(전공의, CodStfDtr) 행 — 없으면 빈 사전 + private readonly IReadOnlyDictionary staffDoctor; + /// 이 내원의 진료과 마스터 행(M_DepMst) — 없으면 빈 사전 private readonly IReadOnlyDictionary department; @@ -187,6 +190,7 @@ public sealed class PatientTagResolver : ITagValueResolver public PatientTagResolver(PatientContext context, ITagValueResolver next, string residentNumber = "", IReadOnlyDictionary? address = null, IReadOnlyDictionary? doctor = null, + IReadOnlyDictionary? staffDoctor = null, IReadOnlyDictionary? department = null, IReadOnlyDictionary? hospital = null, Func>>? diagnosisSource = null, @@ -198,6 +202,7 @@ public sealed class PatientTagResolver : ITagValueResolver this.residentNumber = residentNumber; this.address = address ?? new Dictionary(StringComparer.OrdinalIgnoreCase); this.doctor = doctor ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + this.staffDoctor = staffDoctor ?? new Dictionary(StringComparer.OrdinalIgnoreCase); this.department = department ?? new Dictionary(StringComparer.OrdinalIgnoreCase); this.hospital = hospital ?? new Dictionary(StringComparer.OrdinalIgnoreCase); this.diagnosisSource = diagnosisSource; @@ -254,6 +259,15 @@ public sealed class PatientTagResolver : ITagValueResolver { return fallback; } + // _Refer(참조 내원) 계열은 "안 옮긴 것"이 아니다 — 참조 내원은 EMR 실행 화면 + // (ActionTag, fmCodLstActionTag.vb:54 등)에서 사용자가 지정하는 값이고, + // 레거시 서식생성기 미리보기(TestPatientSetting)도 그것을 설정하지 않아 + // 레거시에서도 이 태그들은 미리보기에서 빈 값이다. 사유를 갈라 말한다. + if (tag.EndsWith("_Refer", StringComparison.Ordinal)) + { + return new TagValue(false, + "참조 내원 태그입니다 — 참조 내원은 EMR 실행 화면에서 지정되며, 레거시 미리보기에서도 빈 값입니다"); + } return new TagValue(false, "환자 태그이지만 아직 옮기지 않았습니다"); } @@ -352,10 +366,22 @@ public sealed class PatientTagResolver : ITagValueResolver private TagValue? FromDoctor(string tag) { if (tag is not ("OCM_담당의사" or "OCM_담당의사_담당의사코드" or "OCM_담당의사_연락처" - or "OCM_담당의사_영문" or "OCM_담당의사면허번호" or "OCM_담당의사전문의번호")) + or "OCM_담당의사_영문" or "OCM_담당의사면허번호" or "OCM_담당의사전문의번호" + or "OCM_STFDTR")) { return null; } + // STFDTR(주치의/전공의)는 같은 진료과 행의 CodStfDtr 로 찾은 별도 행이다 + // (bzDataInterface.vb:4026-4067 — 조회 갈래는 담당의와 같고 코드 컬럼만 다르다) + if (tag == "OCM_STFDTR") + { + var stf = staffDoctor.TryGetValue("UidNam", out var sn) ? sn.Trim() : string.Empty; + return stf.Length > 0 + ? new TagValue(true, stf) + : new TagValue(false, staffDoctor.Count == 0 + ? "이 내원에 주치의(전공의)가 지정되지 않았습니다" + : "주치의 이름이 비어 있습니다"); + } string Field(string column) => doctor.TryGetValue(column, out var v) ? v.Trim() : string.Empty; var name = Field("UidNam"); @@ -698,7 +724,7 @@ public sealed class PatientTagResolver : ITagValueResolver "ETC_요양기관명칭_병원전화_팩스번호", "ETC_요양기관명칭_영문_병원전화_팩스번호", "ETC_요양기관명칭_병원주소", "ETC_요양기관명칭_병원주소_영문", "ETC_요양기관명칭_사업자등록번호", "ETC_요양기관기호", - "OCM_담당의사_영문", "OCM_담당의사면허번호", "OCM_담당의사전문의번호", + "OCM_담당의사_영문", "OCM_담당의사면허번호", "OCM_담당의사전문의번호", "OCM_STFDTR", "OCM_User진료과", "OCM_User진료과_한글명칭", "OCM_User진료의사명", "OCM_상병명_가로", "OCM_상병명_세로", "OCM_상병명_영어_가로", "OCM_상병명_영어_세로", "OCM_상병코드_가로", "OCM_상병코드_세로", "OCM_부상병코드", "OCM_부상병POA", diff --git a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs index 01da328..a569bb1 100644 --- a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs +++ b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs @@ -223,7 +223,8 @@ public partial class PreviewWindow : Window ? session : new PatientTagResolver(picked, session, PatientIdentity.ResidentNumberOf(picked), PatientIdentity.AddressOf(picked), - PatientIdentity.DoctorOf(picked), PatientIdentity.DepartmentOf(picked), + PatientIdentity.DoctorOf(picked), PatientIdentity.StaffDoctorOf(picked), + PatientIdentity.DepartmentOf(picked), PatientIdentity.HospitalRowOf(picked), PatientIdentity.DiagnosisSourceFor(picked), PatientIdentity.SurgerySourceFor(picked),