diff --git a/src/SheetMe.Data/Stores/ReviewDiagnosisStore.cs b/src/SheetMe.Data/Stores/ReviewDiagnosisStore.cs
new file mode 100644
index 0000000..6bd95df
--- /dev/null
+++ b/src/SheetMe.Data/Stores/ReviewDiagnosisStore.cs
@@ -0,0 +1,109 @@
+using Oracle.ManagedDataAccess.Client;
+
+namespace SheetMe.Data.Stores;
+
+///
+/// 심사 상병 조회 — 레거시 Bkd_상병쿼리(bzDataInterface.vb:14625-14811)의 이식.
+/// (O_OkdInf)와 구조가 같고 표만 B_OkdInf(심사분)다.
+/// 병원 분기는 KIMEYE 하나뿐이라 그 병원만 미이식 안내가 필요하다.
+/// SQL 은 원문 기계 추출(52줄) — 블록1 UNION 블록2, 주/부 필터는 각 블록의 RN 접기 앞.
+///
+public sealed class ReviewDiagnosisStore
+{
+ #region Member Fields
+ private readonly string connectionString;
+
+ private const string Template = @"
+SELECT *
+FROM (
+ SELECT *
+ FROM (
+ SELECT OkdKcdCod, OkdNam, OkdKcdSeq, OkdDspSeq, OkdCfmSus, OkdEntDtf
+ , KcdCod, KcdElcCod, KcdKorNam, KcdEngNam
+ , ROW_NUMBER() OVER(PARTITION BY KcdCod ORDER BY KcdStrDte DESC) AS OKD_RN
+ FROM B_OkdInf
+ INNER JOIN P_ComInf
+ ON OkdComNum = ComNum
+ INNER JOIN M_KcdMst
+ ON KcdCod = TRIM(OkdKcdCod)
+ AND OkdKcdSeq = KcdSeq
+ AND KcdEndDte >= SUBSTR(ComAcpDtm, 1, 8)
+ AND KcdStrDte <= SUBSTR(ComAcpDtm, 1, 8)
+ WHERE OkdComNum = :piComNum
+ AND OkdStt = 'E'
+{TYPE}
+ ) OKD
+ WHERE OKD_RN = 1
+
+ UNION
+
+ SELECT *
+ FROM (
+ SELECT OkdKcdCod, OkdNam, OkdKcdSeq, OkdDspSeq, OkdCfmSus, OkdEntDtf
+ , KCD.KcdCod, KCD.KcdElcCod, KCD.KcdKorNam, KCD.KcdEngNam
+ , ROW_NUMBER() OVER(PARTITION BY KCD.KcdCod ORDER BY KCD.KcdStrDte DESC) AS OKD_RN
+ FROM (
+ SELECT OkdKcdCod, OkdNam, OkdKcdSeq, OkdDspSeq, OkdCfmSus, OkdEntDtf
+ , KcdCod, KcdElcCod, KcdKorNam, KcdEngNam
+ FROM B_OkdInf
+ INNER JOIN P_ComInf
+ ON OkdComNum = ComNum
+ LEFT JOIN M_KcdMst
+ ON KcdCod = TRIM(OkdKcdCod)
+ AND OkdKcdSeq = KcdSeq
+ AND KcdEndDte >= SUBSTR(ComAcpDtm, 1, 8)
+ AND KcdStrDte <= SUBSTR(ComAcpDtm, 1, 8)
+ WHERE OkdComNum = :piComNum
+ AND OkdStt = 'E'
+ ) OKD
+ INNER JOIN M_KcdMst KCD
+ ON KCD.KcdCod = TRIM(OkdKcdCod)
+ AND OkdKcdSeq = KCD.KcdSeq
+ WHERE 1=1
+ AND (OKD.KcdCod IS NULL OR OKD.KcdCod = ' ')
+{TYPE}
+ ) OKD
+ WHERE OKD_RN = 1
+ )
+ORDER BY OkdDspSeq
+";
+ #endregion
+
+ #region Constructors
+ public ReviewDiagnosisStore(string connectionString) => this.connectionString = connectionString;
+ #endregion
+
+ #region Methods
+ /// 이 내원의 심사 상병 행들 — 표시 순서대로. 없으면 빈 목록
+ public List> Diagnoses(decimal comNum, DiagnosisStore.Scope scope)
+ {
+ var filter = scope switch
+ {
+ DiagnosisStore.Scope.Main => " AND OkdDspSeq = '1' ",
+ DiagnosisStore.Scope.Sub => " AND OkdDspSeq <> '1' ",
+ _ => string.Empty,
+ };
+ using var connection = new OracleConnection(connectionString);
+ connection.Open();
+ using var command = connection.CreateCommand();
+ command.BindByName = true;
+ command.CommandText = Template.Replace("{TYPE}", filter);
+ command.Parameters.Add(new OracleParameter("piComNum", comNum));
+
+ var rows = new List>();
+ using var reader = command.ExecuteReader();
+ while (reader.Read())
+ {
+ var row = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ for (var i = 0; i < reader.FieldCount; i++)
+ {
+ row[reader.GetName(i)] = reader.IsDBNull(i)
+ ? string.Empty
+ : reader.GetValue(i).ToString()!.Trim();
+ }
+ rows.Add(row);
+ }
+ return rows;
+ }
+ #endregion
+}
diff --git a/src/SheetMe.Designer/Services/PatientIdentity.cs b/src/SheetMe.Designer/Services/PatientIdentity.cs
index f89fe5a..f0299ea 100644
--- a/src/SheetMe.Designer/Services/PatientIdentity.cs
+++ b/src/SheetMe.Designer/Services/PatientIdentity.cs
@@ -109,6 +109,16 @@ public static class PatientIdentity
: new DiagnosisStore(connection).Diagnoses(context.ComNum, scope);
}
+ /// 심사 상병(B_OkdInf) 공급자 — 상병과 같은 정책
+ public static Func>>
+ ReviewDiagnosisSourceFor(PatientContext context)
+ {
+ var connection = ConfigService.Current.ConnectionString;
+ return scope => connection.Length == 0
+ ? new List>()
+ : new ReviewDiagnosisStore(connection).Diagnoses(context.ComNum, scope);
+ }
+
/// 수술 행 공급자 — 상병과 같은 정책(필요할 때 1회, 실패는 던져 사유로)
public static Func>> SurgerySourceFor(PatientContext context)
{
diff --git a/src/SheetMe.Designer/Services/PatientSession.cs b/src/SheetMe.Designer/Services/PatientSession.cs
index 1de2040..b7dfdd4 100644
--- a/src/SheetMe.Designer/Services/PatientSession.cs
+++ b/src/SheetMe.Designer/Services/PatientSession.cs
@@ -69,7 +69,8 @@ public static class PatientSession
PatientIdentity.DoctorOf(picked), PatientIdentity.DepartmentOf(picked),
PatientIdentity.HospitalNameOf(picked),
PatientIdentity.DiagnosisSourceFor(picked),
- PatientIdentity.SurgerySourceFor(picked))
+ PatientIdentity.SurgerySourceFor(picked),
+ PatientIdentity.ReviewDiagnosisSourceFor(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 8638c8a..bcc7e12 100644
--- a/src/SheetMe.Designer/Services/PatientTagResolver.cs
+++ b/src/SheetMe.Designer/Services/PatientTagResolver.cs
@@ -179,12 +179,18 @@ public sealed class PatientTagResolver : ITagValueResolver
private IReadOnlyList>? surgeryCache;
+ /// 심사 상병(B_OkdInf) 공급자 — 상병과 같은 정책
+ private readonly Func>>? reviewSource;
+
+ private readonly Dictionary>> reviewCache = new();
+
public PatientTagResolver(PatientContext context, ITagValueResolver next,
string residentNumber = "", IReadOnlyDictionary? address = null,
IReadOnlyDictionary? doctor = null,
IReadOnlyDictionary? department = null, string hospitalName = "",
Func>>? diagnosisSource = null,
- Func>>? surgerySource = null)
+ Func>>? surgerySource = null,
+ Func>>? reviewSource = null)
{
this.context = context;
this.next = next;
@@ -195,6 +201,7 @@ public sealed class PatientTagResolver : ITagValueResolver
this.hospitalName = hospitalName;
this.diagnosisSource = diagnosisSource;
this.surgerySource = surgerySource;
+ this.reviewSource = reviewSource;
}
#endregion
@@ -226,6 +233,10 @@ public sealed class PatientTagResolver : ITagValueResolver
{
return surgeryValue;
}
+ if (FromReviewDiagnosis(tag) is { } reviewValue)
+ {
+ return reviewValue;
+ }
if (Computed.TryGetValue(tag, out var compute))
{
var made = compute(context);
@@ -562,6 +573,60 @@ public sealed class PatientTagResolver : ITagValueResolver
: new TagValue(false, $"수술 행은 있는데 {column} 값이 비어 있습니다");
}
+ ///
+ /// 심사 상병 4종 — B_OkdInf(심사분). 결합 규칙(본문 확인):
+ /// 주상병명/코드는 첫 행, 부상병명/코드는 줄바꿈 결합(가로형이 없다).
+ /// 병원 분기는 KIMEYE 하나뿐이다(Bkd_상병쿼리:14631).
+ ///
+ private TagValue? FromReviewDiagnosis(string tag)
+ {
+ var scope = tag switch
+ {
+ "OCM_심사_주상병명" or "OCM_심사_주상병코드" => DiagnosisStore.Scope.Main,
+ "OCM_심사_부상병명" or "OCM_심사_부상병코드" => DiagnosisStore.Scope.Sub,
+ _ => (DiagnosisStore.Scope?)null,
+ } is { } s ? s : (DiagnosisStore.Scope?)null;
+ if (scope is null)
+ {
+ return null;
+ }
+ if (UserSession.Current.HspCod == "KIMEYE")
+ {
+ return new TagValue(false, "이 병원(KIMEYE)의 심사 상병 분기는 아직 옮기지 않았습니다");
+ }
+ if (reviewSource is null)
+ {
+ return new TagValue(false, "심사 상병 조회가 연결되지 않았습니다");
+ }
+ if (!reviewCache.TryGetValue(scope.Value, out var rows))
+ {
+ try
+ {
+ rows = reviewSource(scope.Value);
+ }
+ catch (Exception ex)
+ {
+ return new TagValue(false, $"심사 상병 조회에 실패했습니다: {ex.GetType().Name}");
+ }
+ reviewCache[scope.Value] = rows;
+ }
+ if (rows.Count == 0)
+ {
+ return new TagValue(false, "이 내원에 심사 상병이 없습니다");
+ }
+ static string Cell(Dictionary r, string c) => r.TryGetValue(c, out var v) ? v.Trim() : string.Empty;
+ var value = tag switch
+ {
+ "OCM_심사_주상병명" => Cell(rows[0], "KcdKorNam"),
+ "OCM_심사_주상병코드" => Cell(rows[0], "KcdElcCod"),
+ "OCM_심사_부상병명" => string.Join(Environment.NewLine, rows.Select(r => Cell(r, "KcdKorNam"))),
+ _ => string.Join(Environment.NewLine, rows.Select(r => Cell(r, "KcdElcCod"))),
+ };
+ return value.Trim().Length > 0
+ ? new TagValue(true, value)
+ : new TagValue(false, "심사 상병 행은 있는데 그 값이 비어 있습니다");
+ }
+
/// 주민번호에서 나온 값 — 비면 왜 비었는지 말한다
private TagValue Wrap(string value) => value.Length > 0
? new TagValue(true, value)
@@ -584,6 +649,7 @@ public sealed class PatientTagResolver : ITagValueResolver
"OCM_주상병명", "OCM_주상병코드", "OCM_주상병POA",
"OCM_OPNAME", "OCM_OPRCODNAM", "OCM_DX", "OCM_수술상병", "OCM_수술부위",
"OCM_수술_OprPatETC", "OCM_수술집도의", "OCM_수술마취의사",
+ "OCM_심사_주상병명", "OCM_심사_주상병코드", "OCM_심사_부상병명", "OCM_심사_부상병코드",
};
///
diff --git a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs
index 663bd1f..416c972 100644
--- a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs
+++ b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs
@@ -226,7 +226,8 @@ public partial class PreviewWindow : Window
PatientIdentity.DoctorOf(picked), PatientIdentity.DepartmentOf(picked),
PatientIdentity.HospitalNameOf(picked),
PatientIdentity.DiagnosisSourceFor(picked),
- PatientIdentity.SurgerySourceFor(picked));
+ PatientIdentity.SurgerySourceFor(picked),
+ PatientIdentity.ReviewDiagnosisSourceFor(picked));
// 러너를 새로 만든다. 같은 러너를 재사용하면 옛 환자의 조회 결과가 캐시에 남아
// 환자를 바꿨는데 표는 그대로가 된다 — 태그만 바뀌고 표는 안 바뀌면 아무도 눈치채지 못한다.
fields = new MDataTableRunner(designer.Document,