진료과 파생·입퇴원과 8종 — 144종 → 152종, OCM_진료과 개원일 분기 복원
진료과 파생 4종(bzDataInterface.vb:5556-6007): 그룹코드("한글명(그룹
코드)" 결합 — 행이 있으면 조각이 비어도 결합), 영어(DepEngNam),
대외명칭(DepOfcNam), 약어명칭(DepBrfNam). 전부 프리페치된 진료과
행(M_DepMst SELECT *)의 다른 컬럼이라 왕복이 늘지 않는다. 레거시의
시점 판정(퇴원→퇴원시점·재원→현재·외래→접수)은 문맥 적용일시와 같은
규칙이다 — 자격이 퇴원보다 먼저 끝난 희귀 케이스만 갈린다(주석).
입원과 2종(:6009-6110)은 접수 시점, 퇴원과 2종(:6162-6318)은
퇴원(ILV)/현재 시점의 진료과 — 문맥과 다른 시점이라
DepartmentCodeAt(GetCodInfDT 대응, 시작일시 최신 1행 고정)과
DischargeDepartment(P_ComInf 에 날짜 조건만으로 M_DepMst 를 조인하는
원문 모양 그대로)를 신설했다. 퇴원과 본체의 If/Else 동일 쿼리(낮병동
주석과 달리 복붙 결함 — 퇴원일시 비면 0행→빈)도 그대로 보존,
한글명칭만 접수일시 폴백이 실제로 있다.
OCM_진료과: 개원일(HspStrDte)이 요양기관 행에 생겨 레거시 분기
(2022-07-01 이후 개원 + 대외명칭 있음 → 대외명칭)를 그대로 복원 —
전에는 개원일이 없어 DepKorNam 으로 고정했었다.
- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 152종)
- --db-patient ①~㉝ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
975aff4211
commit
c0ad782b6a
@@ -185,6 +185,13 @@ public static class PatientIdentity
|
||||
return () => connection.Length == 0 ? null : new PatientExtraStore(connection);
|
||||
}
|
||||
|
||||
/// <summary>문맥 저장소 공급자 — 입원과·퇴원과의 시점별 진료과 조회용</summary>
|
||||
public static Func<PatientContextStore?> ContextStoreSource()
|
||||
{
|
||||
var connection = ConfigService.Current.ConnectionString;
|
||||
return () => connection.Length == 0 ? null : new PatientContextStore(connection);
|
||||
}
|
||||
|
||||
/// <summary>수술 일정(S_OprInf) 공급자 — 최신순 여부별 1회</summary>
|
||||
public static Func<bool, IReadOnlyDictionary<string, string>> ScheduledSurgerySourceFor(PatientContext context)
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ public static class PatientSession
|
||||
PatientIdentity.VitalSourceFor(picked),
|
||||
PatientIdentity.ScheduledSurgerySourceFor(picked),
|
||||
PatientIdentity.PathologyDoctorSource(), PatientIdentity.LabDoctorSource(),
|
||||
PatientIdentity.ExtraStoreSource())
|
||||
PatientIdentity.ExtraStoreSource(), PatientIdentity.ContextStoreSource())
|
||||
: session;
|
||||
return (tags, new MDataTableRunner(document, Variables()));
|
||||
}
|
||||
|
||||
@@ -231,6 +231,14 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
|
||||
private readonly Dictionary<string, TagValue> extraCache = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// 문맥 저장소 공급자 — 입원과·퇴원과처럼 문맥의 적용일시와 <b>다른 시점</b>을
|
||||
/// 물어야 하는 태그가 쓴다(접수 시점·퇴원 시점의 진료과).
|
||||
/// </summary>
|
||||
private readonly Func<PatientContextStore?>? contextStoreSource;
|
||||
|
||||
private readonly Dictionary<string, TagValue> deptAtCache = new(StringComparer.Ordinal);
|
||||
|
||||
private readonly Dictionary<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>> reviewCache = new();
|
||||
|
||||
public PatientTagResolver(PatientContext context, ITagValueResolver next,
|
||||
@@ -246,7 +254,8 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
Func<bool, IReadOnlyDictionary<string, string>>? scheduledSurgerySource = null,
|
||||
Func<IReadOnlyDictionary<string, string>>? pathologyDoctorSource = null,
|
||||
Func<IReadOnlyDictionary<string, string>>? labDoctorSource = null,
|
||||
Func<PatientExtraStore?>? extraStoreSource = null)
|
||||
Func<PatientExtraStore?>? extraStoreSource = null,
|
||||
Func<PatientContextStore?>? contextStoreSource = null)
|
||||
{
|
||||
this.context = context;
|
||||
this.next = next;
|
||||
@@ -264,6 +273,7 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
this.pathologyDoctorSource = pathologyDoctorSource;
|
||||
this.labDoctorSource = labDoctorSource;
|
||||
this.extraStoreSource = extraStoreSource;
|
||||
this.contextStoreSource = contextStoreSource;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -319,6 +329,10 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
{
|
||||
return stayValue;
|
||||
}
|
||||
if (FromDeptAt(tag) is { } deptAtValue)
|
||||
{
|
||||
return deptAtValue;
|
||||
}
|
||||
if (Computed.TryGetValue(tag, out var compute))
|
||||
{
|
||||
var made = compute(context);
|
||||
@@ -514,14 +528,28 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
/// </summary>
|
||||
private TagValue? FromFacility(string tag)
|
||||
{
|
||||
string Dep(string column) => department.TryGetValue(column, out var v) ? v.Trim() : string.Empty;
|
||||
TagValue DeptColumn(string column, string reasonWhenEmpty)
|
||||
{
|
||||
if (department.Count == 0)
|
||||
{
|
||||
return new TagValue(false, "이 내원의 진료과를 찾지 못했습니다");
|
||||
}
|
||||
var value = Dep(column);
|
||||
return value.Length > 0 ? new TagValue(true, value) : new TagValue(false, reasonWhenEmpty);
|
||||
}
|
||||
switch (tag)
|
||||
{
|
||||
case "OCM_진료과":
|
||||
{
|
||||
string Dep(string column) => department.TryGetValue(column, out var v) ? v.Trim() : string.Empty;
|
||||
// 이름 선택(bzDataInterface.vb:5474-5484): NH 는 무조건 DepOfcNam,
|
||||
// 그 밖은 개원일 2022-07-01 이후 + DepOfcNam 있음 → DepOfcNam, 아니면 DepKorNam.
|
||||
// 개원일은 요양기관 행의 HspStrDte 다 — 처음엔 없어서 DepKorNam 으로 고정했었다.
|
||||
var office = Dep("DepOfcNam");
|
||||
var value = string.Equals(UserSession.Current.HspCod, "NH", StringComparison.OrdinalIgnoreCase)
|
||||
? office
|
||||
: string.CompareOrdinal(PatientContext.Value(hospital, "HspStrDte"), "20220701") >= 0
|
||||
&& office.Length > 0 ? office
|
||||
: Dep("DepKorNam");
|
||||
return value.Length > 0
|
||||
? new TagValue(true, value)
|
||||
@@ -529,6 +557,20 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
? "이 내원의 진료과를 찾지 못했습니다"
|
||||
: "진료과 마스터에 이름이 비어 있습니다");
|
||||
}
|
||||
// ── 진료과 파생 4종 — 같은 행(M_DepMst SELECT *)의 다른 컬럼(:5556-6007) ──
|
||||
// 레거시의 시점(퇴원했으면 퇴원 시점, 재원 중이면 현재, 외래면 접수)은 문맥
|
||||
// 적용일시와 같은 판정이다 — 자격이 퇴원보다 먼저 끝난 희귀 케이스에서만 갈린다.
|
||||
case "OCM_진료과_그룹코드":
|
||||
// 행이 있으면 무조건 "이름(그룹코드)" 결합이다(:5593) — 조각이 비어도 그대로
|
||||
return department.Count == 0
|
||||
? new TagValue(false, "이 내원의 진료과를 찾지 못했습니다")
|
||||
: new TagValue(true, $"{Dep("DepKorNam")}({Dep("DepGrpCod")})");
|
||||
case "OCM_진료과_영어":
|
||||
return DeptColumn("DepEngNam", "진료과 마스터에 영문 이름이 비어 있습니다");
|
||||
case "OCM_진료과대외명칭":
|
||||
return DeptColumn("DepOfcNam", "진료과 마스터에 대외 명칭이 비어 있습니다");
|
||||
case "OCM_진료과약어명칭":
|
||||
return DeptColumn("DepBrfNam", "진료과 마스터에 약어 명칭이 비어 있습니다");
|
||||
// ── 요양기관 12종 — 같은 조인의 다른 컬럼(bzDataInterface.vb:15656-16709) ──
|
||||
// 접미·결합도 본문 그대로: 병원장 = 병원명+"장", 귀하 = +"장 귀하",
|
||||
// 전화_팩스 = "Tel : … FAX : …", 영문판은 "+82 - " 접두가 붙는다.
|
||||
@@ -895,6 +937,105 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
: new TagValue(false, "수술일자 값이 형식에 맞지 않습니다");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 입원과·퇴원과 4종(bzDataInterface.vb:6009-6318) — 문맥의 적용일시와 <b>다른 시점</b>의
|
||||
/// 진료과를 묻는다. 입원과는 <b>접수 시점</b>(전과했으면 처음 과), 퇴원과는
|
||||
/// 퇴원(ILV → ComLevDtm) 또는 현재 시점의 과다.
|
||||
///
|
||||
/// 퇴원과 본체(:6194-6210)는 "낮병동은 퇴원일시가 빈다"는 주석과 달리 If/Else 가
|
||||
/// <b>똑같은 쿼리</b>다(복붙 결함으로 보인다 — ComLevDtm 이 비면 0행 → 빈 값).
|
||||
/// 한글명칭(:6265-6281)만 빈 퇴원일시에서 접수일시로 대신한다. 둘 다 그대로 옮겼다.
|
||||
/// </summary>
|
||||
private TagValue? FromDeptAt(string tag)
|
||||
{
|
||||
var admission = tag is "OCM_입원과" or "OCM_입원과_한글명칭";
|
||||
var discharge = tag is "OCM_퇴원과" or "OCM_퇴원과_한글명칭";
|
||||
if (!admission && !discharge)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (deptAtCache.TryGetValue(tag, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
var value = Compute();
|
||||
deptAtCache[tag] = value;
|
||||
return value;
|
||||
|
||||
TagValue Compute()
|
||||
{
|
||||
if (contextStoreSource is null)
|
||||
{
|
||||
return new TagValue(false, "진료과 시점 조회가 연결되지 않았습니다");
|
||||
}
|
||||
var comNum = PatientContext.Value(context.ComInf, "ComNum");
|
||||
if (!decimal.TryParse(comNum, out var m))
|
||||
{
|
||||
return new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
|
||||
}
|
||||
try
|
||||
{
|
||||
var store = contextStoreSource();
|
||||
if (store is null)
|
||||
{
|
||||
return new TagValue(false, "진료과 시점 조회가 연결되지 않았습니다");
|
||||
}
|
||||
var open = PatientContext.Value(hospital, "HspStrDte");
|
||||
string PickName(IReadOnlyDictionary<string, string> row)
|
||||
{
|
||||
// 개원일 2022-07-01 이후 + 대외명칭 있음 → 대외명칭, 아니면 한글명(:6044-6048)
|
||||
var office = PatientContext.Value(row, "DepOfcNam").Trim();
|
||||
return string.CompareOrdinal(open, "20220701") >= 0 && office.Length > 0
|
||||
? office
|
||||
: PatientContext.Value(row, "DepKorNam").Trim();
|
||||
}
|
||||
if (admission)
|
||||
{
|
||||
var acp = PatientContext.Value(context.ComInf, "ComAcpDtm");
|
||||
if (acp.Length < 12)
|
||||
{
|
||||
return new TagValue(false, "이 내원의 접수일시를 읽지 못했습니다");
|
||||
}
|
||||
var depCod = store.DepartmentCodeAt(m, acp[..12]);
|
||||
if (depCod.Length == 0)
|
||||
{
|
||||
return new TagValue(false, "접수 시점의 진료 행(P_CodInf)이 없습니다");
|
||||
}
|
||||
var row = store.Department(depCod, acp[..8]);
|
||||
if (row.Count == 0)
|
||||
{
|
||||
return new TagValue(true, string.Empty); // 레거시도 마스터 0행이면 "" 다
|
||||
}
|
||||
return new TagValue(true, tag == "OCM_입원과"
|
||||
? PickName(row)
|
||||
: PatientContext.Value(row, "DepKorNam").Trim());
|
||||
}
|
||||
var prg = PatientContext.Value(context.ComInf, "ComPrgStt");
|
||||
var lev = PatientContext.Value(context.ComInf, "ComLevDtm");
|
||||
var depAtLeave = store.DepartmentCodeAt(m,
|
||||
prg == "ILV" && lev.Length >= 12 ? lev[..12] : null);
|
||||
if (depAtLeave.Length == 0)
|
||||
{
|
||||
return new TagValue(true, string.Empty); // 레거시가 명시적으로 "" 를 돌려준다(:6189)
|
||||
}
|
||||
var anchor = tag == "OCM_퇴원과_한글명칭" && lev.Trim().Length == 0
|
||||
? "ComAcpDtm" : "ComLevDtm";
|
||||
var deptRow = store.DischargeDepartment(m, depAtLeave, anchor);
|
||||
if (deptRow.Count == 0)
|
||||
{
|
||||
return new TagValue(true, string.Empty);
|
||||
}
|
||||
return new TagValue(true, tag == "OCM_퇴원과"
|
||||
? PickName(deptRow)
|
||||
: PatientContext.Value(deptRow, "DepKorNam").Trim());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new TagValue(false, $"진료과 시점 조회에 실패했습니다: {ex.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 입·퇴원 일시와 재원일수 12종(bzDataInterface.vb:7152-7940, 전부 Case Else 갈래).
|
||||
///
|
||||
@@ -1275,6 +1416,8 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
"PAT_건보_급여_세대주명", "PAT_협력업체", "PAT_보호자연락처",
|
||||
"OCM_FollowUp", "OCM_외출외박신청일", "OCM_외출외박종료일",
|
||||
"OCM_최초내원일", "OCM_초진일_발병일", "OCM_신환_초진일",
|
||||
"OCM_진료과_그룹코드", "OCM_진료과_영어", "OCM_진료과대외명칭", "OCM_진료과약어명칭",
|
||||
"OCM_입원과", "OCM_입원과_한글명칭", "OCM_퇴원과", "OCM_퇴원과_한글명칭",
|
||||
"OCM_입원시간", "OCM_입원일시", "OCM_입원일시_영문", "OCM_입실시간",
|
||||
"OCM_의사퇴원예고일시", "OCM_퇴원예정일시", "OCM_퇴원시간", "OCM_퇴원일시",
|
||||
"OCM_퇴원예고일시", "OCM_재원일수", "OCM_퇴원예고재원일수", "OCM_입원일자_낮병동",
|
||||
|
||||
@@ -232,7 +232,7 @@ public partial class PreviewWindow : Window
|
||||
PatientIdentity.VitalSourceFor(picked),
|
||||
PatientIdentity.ScheduledSurgerySourceFor(picked),
|
||||
PatientIdentity.PathologyDoctorSource(), PatientIdentity.LabDoctorSource(),
|
||||
PatientIdentity.ExtraStoreSource());
|
||||
PatientIdentity.ExtraStoreSource(), PatientIdentity.ContextStoreSource());
|
||||
// 러너를 <b>새로</b> 만든다. 같은 러너를 재사용하면 옛 환자의 조회 결과가 캐시에 남아
|
||||
// 환자를 바꿨는데 표는 그대로가 된다 — 태그만 바뀌고 표는 안 바뀌면 아무도 눈치채지 못한다.
|
||||
fields = new MDataTableRunner(designer.Document,
|
||||
|
||||
Reference in New Issue
Block a user