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,