diff --git a/src/SheetMe.Data/Stores/PatientContextStore.cs b/src/SheetMe.Data/Stores/PatientContextStore.cs index 759772e..500c781 100644 --- a/src/SheetMe.Data/Stores/PatientContextStore.cs +++ b/src/SheetMe.Data/Stores/PatientContextStore.cs @@ -226,6 +226,22 @@ public sealed class PatientContextStore ("d", code), ("y", date[..8])); } + /// + /// 수술 일정(S_OprInf) 한 행 — OCM_수술일자·ETC_수술일자_* 계열이 쓴다. + /// WHERE OprComNum = :c AND OprStt = 'E' ORDER BY OprKey — + /// 무인자(Else 갈래)는 ASC(첫 수술), ETC_ 계열과 마지막수술은 DESC('수술실 요청' 주석 그대로). + /// GetOprInfDT(E_OprInf 축 큰 조회)와 다른 표다 — 이쪽은 수술 접수 일정이다. + /// + public IReadOnlyDictionary ScheduledSurgery(decimal comNum, bool latestFirst) + { + using var connection = new OracleConnection(connectionString); + connection.Open(); + return Row(connection, + "SELECT * FROM (SELECT * FROM S_OprInf WHERE OprComNum = :c AND OprStt = 'E'" + + $" ORDER BY OprKey{(latestFirst ? " DESC" : string.Empty)}) WHERE ROWNUM = 1", + ("c", comNum)); + } + /// /// 건물관리번호로 주소 — 레거시 GetZipInfoByBuildingNumber(clsCommonLib.vb:16600). /// diff --git a/src/SheetMe.Designer/Services/PatientIdentity.cs b/src/SheetMe.Designer/Services/PatientIdentity.cs index 949a011..f7ae446 100644 --- a/src/SheetMe.Designer/Services/PatientIdentity.cs +++ b/src/SheetMe.Designer/Services/PatientIdentity.cs @@ -161,6 +161,15 @@ public static class PatientIdentity : new VitalStore(connection).One(context.ComNum, expr, pick, notNull, order); } + /// 수술 일정(S_OprInf) 공급자 — 최신순 여부별 1회 + public static Func> ScheduledSurgerySourceFor(PatientContext context) + { + var connection = ConfigService.Current.ConnectionString; + return latest => connection.Length == 0 + ? new Dictionary(StringComparer.OrdinalIgnoreCase) + : new PatientContextStore(connection).ScheduledSurgery(context.ComNum, latest); + } + /// 수술 행 공급자 — 상병과 같은 정책(필요할 때 1회, 실패는 던져 사유로) public static Func>> SurgerySourceFor(PatientContext context) { diff --git a/src/SheetMe.Designer/Services/PatientSession.cs b/src/SheetMe.Designer/Services/PatientSession.cs index 11e6379..e4705bc 100644 --- a/src/SheetMe.Designer/Services/PatientSession.cs +++ b/src/SheetMe.Designer/Services/PatientSession.cs @@ -72,7 +72,8 @@ public static class PatientSession PatientIdentity.DiagnosisSourceFor(picked), PatientIdentity.SurgerySourceFor(picked), PatientIdentity.ReviewDiagnosisSourceFor(picked), - PatientIdentity.VitalSourceFor(picked)) + PatientIdentity.VitalSourceFor(picked), + PatientIdentity.ScheduledSurgerySourceFor(picked)) : session; return (tags, new MDataTableRunner(document, Variables())); } diff --git a/src/SheetMe.Designer/Services/PatientTagResolver.cs b/src/SheetMe.Designer/Services/PatientTagResolver.cs index 8804abb..e8acd66 100644 --- a/src/SheetMe.Designer/Services/PatientTagResolver.cs +++ b/src/SheetMe.Designer/Services/PatientTagResolver.cs @@ -190,6 +190,11 @@ public sealed class PatientTagResolver : ITagValueResolver private readonly Dictionary vitalCache = new(StringComparer.Ordinal); + /// 수술 일정 공급자 — (최신순?) 별 1회 캐시 + private readonly Func>? scheduledSurgerySource; + + private readonly Dictionary> scheduledSurgeryCache = new(); + private readonly Dictionary>> reviewCache = new(); public PatientTagResolver(PatientContext context, ITagValueResolver next, @@ -201,7 +206,8 @@ public sealed class PatientTagResolver : ITagValueResolver Func>>? diagnosisSource = null, Func>>? surgerySource = null, Func>>? reviewSource = null, - Func? vitalSource = null) + Func? vitalSource = null, + Func>? scheduledSurgerySource = null) { this.context = context; this.next = next; @@ -215,6 +221,7 @@ public sealed class PatientTagResolver : ITagValueResolver this.surgerySource = surgerySource; this.reviewSource = reviewSource; this.vitalSource = vitalSource; + this.scheduledSurgerySource = scheduledSurgerySource; } #endregion @@ -254,6 +261,10 @@ public sealed class PatientTagResolver : ITagValueResolver { return vitalValue; } + if (FromScheduledSurgery(tag) is { } scheduleValue) + { + return scheduleValue; + } if (Computed.TryGetValue(tag, out var compute)) { var made = compute(context); @@ -745,6 +756,66 @@ public sealed class PatientTagResolver : ITagValueResolver new[] { "EmrAdpTim" }, "EmrAdpTim DESC"), }; + /// + /// 수술 일정(S_OprInf) 6종 — 해당 태그가 아니면 null. + /// ETC_수술일자_* 와 마지막수술은 DESC(마지막), OCM_수술일자는 Else 갈래가 ASC(첫). + /// OCM_수술일자의 병원 분기(PURME 주사오더 / BSGH·HIMCHAN_*·GJHNSS DESC)는 미이식 안내. + /// + private TagValue? FromScheduledSurgery(string tag) + { + var latest = tag switch + { + "ETC_수술일자_1_몇년" or "ETC_수술일자_1_몇년2자리" + or "ETC_수술일자_2_몇월" or "ETC_수술일자_3_몇일" + or "OCM_수술일자_마지막수술" => true, + "OCM_수술일자" => false, + _ => (bool?)null, + } is { } l ? l : (bool?)null; + if (latest is null) + { + return null; + } + var hsp = UserSession.Current.HspCod; + if (tag == "OCM_수술일자" + && (hsp is "PURME" or "BSGH" or "GJHNSS" + || hsp.StartsWith("HIMCHAN_", StringComparison.Ordinal))) + { + return new TagValue(false, $"이 병원({hsp})의 수술일자 분기는 아직 옮기지 않았습니다"); + } + if (scheduledSurgerySource is null) + { + return new TagValue(false, "수술 일정 조회가 연결되지 않았습니다"); + } + if (!scheduledSurgeryCache.TryGetValue(latest.Value, out var row)) + { + try + { + row = scheduledSurgerySource(latest.Value); + } + catch (Exception ex) + { + return new TagValue(false, $"수술 일정 조회에 실패했습니다: {ex.GetType().Name}"); + } + scheduledSurgeryCache[latest.Value] = row; + } + var oprDte = row.TryGetValue("OprDte", out var d) ? d.Trim() : string.Empty; + if (row.Count == 0 || oprDte.Length == 0) + { + return new TagValue(false, "이 내원에 수술 일정이 없습니다"); + } + var value = tag switch + { + "ETC_수술일자_1_몇년" => Head(oprDte, 4), + "ETC_수술일자_1_몇년2자리" => oprDte.Length >= 4 ? oprDte.Substring(2, 2) : string.Empty, + "ETC_수술일자_2_몇월" => oprDte.Length >= 6 ? oprDte.Substring(4, 2) : string.Empty, + "ETC_수술일자_3_몇일" => oprDte.Length >= 8 ? oprDte.Substring(6, 2) : string.Empty, + _ => Hyphenate(Head(oprDte, 8)), + }; + return value.Length > 0 + ? new TagValue(true, value) + : new TagValue(false, "수술일자 값이 형식에 맞지 않습니다"); + } + private TagValue? FromVital(string tag) { // 머리둘레 두 종은 레거시 결함이다 — SELECT 는 EmdHc AS HC 인데 Item("BP") 를 읽어 @@ -813,6 +884,8 @@ public sealed class PatientTagResolver : ITagValueResolver "OCM_맥박", "OCM_맥박_LAST", "OCM_호흡", "OCM_호흡_LAST", "OCM_SPO2", "OCM_SPO2_LAST", "OCM_혈압", "OCM_혈압_LAST", "OCM_혈압_BPS", "OCM_혈압_BPD", "OCM_VITAL접수일시_LAST", "OCM_머리둘레", "OCM_머리둘레_LAST", + "OCM_수술일자", "OCM_수술일자_마지막수술", "ETC_수술일자_1_몇년", + "ETC_수술일자_1_몇년2자리", "ETC_수술일자_2_몇월", "ETC_수술일자_3_몇일", }; /// diff --git a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs index ecd8a36..7b7c62c 100644 --- a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs +++ b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs @@ -229,7 +229,8 @@ public partial class PreviewWindow : Window PatientIdentity.DiagnosisSourceFor(picked), PatientIdentity.SurgerySourceFor(picked), PatientIdentity.ReviewDiagnosisSourceFor(picked), - PatientIdentity.VitalSourceFor(picked)); + PatientIdentity.VitalSourceFor(picked), + PatientIdentity.ScheduledSurgerySourceFor(picked)); // 러너를 새로 만든다. 같은 러너를 재사용하면 옛 환자의 조회 결과가 캐시에 남아 // 환자를 바꿨는데 표는 그대로가 된다 — 태그만 바뀌고 표는 안 바뀌면 아무도 눈치채지 못한다. fields = new MDataTableRunner(designer.Document,