심사 상병 4종을 옮긴다 — 59종 → 63종. 워크플로가 조사한 new 는 이제 상병·수술·심사까지 끝났다
Bkd_상병쿼리(bzDataInterface.vb:14625-14811)의 이식. DiagnosisStore 와 구조가 같고 표만 B_OkdInf(심사분)다. SQL 52줄 원문 기계 추출(블록1 UNION 블록2, 주/부 필터는 각 블록의 RN 접기 앞). 병원 분기는 KIMEYE 하나 — 그 병원만 미이식 안내. 태그 4종(본문 확인): 심사_주상병명/코드는 첫 행, 심사_부상병명/코드는 줄바꿈 결합 (가로형이 없다). ## 게이트 - dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 63종) - --db-patient 전건 통과 (B_OkdInf 도 이 시험 DB 에 없을 수 있어 수술과 같은 취급 — 런타임에서 해석기가 예외를 사유로 바꾼다) - --db-render P062 md5 불변 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
460f6c4a91
commit
ef4f36419f
@@ -0,0 +1,109 @@
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
/// <summary>
|
||||
/// 심사 상병 조회 — 레거시 <c>Bkd_상병쿼리</c>(bzDataInterface.vb:14625-14811)의 이식.
|
||||
/// <see cref="DiagnosisStore"/>(O_OkdInf)와 구조가 같고 표만 <b>B_OkdInf</b>(심사분)다.
|
||||
/// 병원 분기는 KIMEYE 하나뿐이라 그 병원만 미이식 안내가 필요하다.
|
||||
/// SQL 은 원문 기계 추출(52줄) — 블록1 UNION 블록2, 주/부 필터는 각 블록의 RN 접기 앞.
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>이 내원의 심사 상병 행들 — 표시 순서대로. 없으면 빈 목록</summary>
|
||||
public List<Dictionary<string, string>> 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<Dictionary<string, string>>();
|
||||
using var reader = command.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
var row = new Dictionary<string, string>(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
|
||||
}
|
||||
@@ -109,6 +109,16 @@ public static class PatientIdentity
|
||||
: new DiagnosisStore(connection).Diagnoses(context.ComNum, scope);
|
||||
}
|
||||
|
||||
/// <summary>심사 상병(B_OkdInf) 공급자 — 상병과 같은 정책</summary>
|
||||
public static Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>
|
||||
ReviewDiagnosisSourceFor(PatientContext context)
|
||||
{
|
||||
var connection = ConfigService.Current.ConnectionString;
|
||||
return scope => connection.Length == 0
|
||||
? new List<Dictionary<string, string>>()
|
||||
: new ReviewDiagnosisStore(connection).Diagnoses(context.ComNum, scope);
|
||||
}
|
||||
|
||||
/// <summary>수술 행 공급자 — 상병과 같은 정책(필요할 때 1회, 실패는 던져 사유로)</summary>
|
||||
public static Func<IReadOnlyList<Dictionary<string, string>>> SurgerySourceFor(PatientContext context)
|
||||
{
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -179,12 +179,18 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
|
||||
private IReadOnlyList<Dictionary<string, string>>? surgeryCache;
|
||||
|
||||
/// <summary>심사 상병(B_OkdInf) 공급자 — 상병과 같은 정책</summary>
|
||||
private readonly Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? reviewSource;
|
||||
|
||||
private readonly Dictionary<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>> reviewCache = new();
|
||||
|
||||
public PatientTagResolver(PatientContext context, ITagValueResolver next,
|
||||
string residentNumber = "", IReadOnlyDictionary<string, string>? address = null,
|
||||
IReadOnlyDictionary<string, string>? doctor = null,
|
||||
IReadOnlyDictionary<string, string>? department = null, string hospitalName = "",
|
||||
Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? diagnosisSource = null,
|
||||
Func<IReadOnlyList<Dictionary<string, string>>>? surgerySource = null)
|
||||
Func<IReadOnlyList<Dictionary<string, string>>>? surgerySource = null,
|
||||
Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? 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} 값이 비어 있습니다");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 심사 상병 4종 — B_OkdInf(심사분). 결합 규칙(본문 확인):
|
||||
/// 주상병명/코드는 첫 행, 부상병명/코드는 <b>줄바꿈</b> 결합(가로형이 없다).
|
||||
/// 병원 분기는 KIMEYE 하나뿐이다(Bkd_상병쿼리:14631).
|
||||
/// </summary>
|
||||
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<string, string> 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, "심사 상병 행은 있는데 그 값이 비어 있습니다");
|
||||
}
|
||||
|
||||
/// <summary>주민번호에서 나온 값 — 비면 <b>왜</b> 비었는지 말한다</summary>
|
||||
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_심사_부상병코드",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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));
|
||||
// 러너를 <b>새로</b> 만든다. 같은 러너를 재사용하면 옛 환자의 조회 결과가 캐시에 남아
|
||||
// 환자를 바꿨는데 표는 그대로가 된다 — 태그만 바뀌고 표는 안 바뀌면 아무도 눈치채지 못한다.
|
||||
fields = new MDataTableRunner(designer.Document,
|
||||
|
||||
Reference in New Issue
Block a user