From f06740ae519fae631fa4b5829c88363414c28d54 Mon Sep 17 00:00:00 2001 From: Msystech Date: Wed, 19 Aug 2026 19:29:33 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A0=81=EC=9A=A9=EC=A7=84=EB=A3=8C=EA=B3=BC?= =?UTF-8?q?=C2=B7=EC=A0=84=EA=B3=BC=C2=B7=EA=B0=84=ED=98=B8=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B3=BC=EA=B1=B0=EB=A0=A5=204=EC=A2=85=20?= =?UTF-8?q?=E2=80=94=20209=EC=A2=85=20=E2=86=92=20213=EC=A2=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OCM_적용진료과(bzDataInterface.vb:5659) — 통합 대표행의 AdpDepCod/AdpDtm 은 진료행 CodDepCod 와 적용일시의 별칭임을 확인 (dtHISOperatingInfo.vb:3699 — CodDepCod AdpDepCod, CodDtrCod AdpDtrCod). 원문이 적용일시 12자리 전체를 8자리 유효기간과 비교하는 특이점 그대로. OCM_적용진료의사(:3939 — AdpDtrCod + 적용일 8자 → UidNam, 기존 UserNameAt 재사용). OCM_전과(:12924 + GetCodInfDT With_Join, dtCommonLib.vb:2777) — 진료행 전부(CodMtiSeq='0', 이름 조인은 오늘 기준·DepWrkTyp IN ('D','A'), SYBS·NYHY 의 'B' 추가 미이식)를 과가 바뀔 때만 "과명 / 시작 ~ 끝" 으로 AppendLine. 원문의 DefaultView 필터·정렬은 순회에 반영 안 되지만 SQL 이 같은 조건·순서(ORDER BY CodStrDtm)라 실질 무해였고, 무기한(2999-12-31) 판정은 Substring(1,8) 결함으로 절대 참이 안 되어 실제 값이 찍힌다 — 판정 없이 그대로. 새 사실: GetCodInfDT 는 항상 ORDER BY CodStrDtm(오름차순)이 있다 — 문맥 로드의 "최신순 1행" 결정과 다른 방향이지만 적용일시 구간에 행이 여럿 겹치는 비정상 데이터에서만 갈린다(기존 결정 유지, 기록만). OCM_간호정보조사지_과거력(:13030 + GetNurseEmrDT :13523 + GetNurseShtCod :13770) — 서식 우선순위(S113→S202→S163, SRCH 갈래)를 CASE 정렬로 접어 레거시 4왕복을 1왕복으로. 병력 결합 " ,"(공백+콤마) 와 무병력 시에도 붙는 개행까지 원문 그대로. GCRCH·SRH·GNBEDRO 의 서식 코드 분기는 미이식. - dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 213종) - --db-patient ①~㉟ 전건 통과 (적용진료과 실값 5자) - --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변 Co-Authored-By: Claude Fable 5 --- src/SheetMe.Data/Stores/PatientExtraStore.cs | 56 +++++++++++ src/SheetMe.Designer/Diagnostics/DbSmoke.cs | 3 + .../Services/PatientTagResolver.cs | 94 +++++++++++++++++++ 3 files changed, 153 insertions(+) diff --git a/src/SheetMe.Data/Stores/PatientExtraStore.cs b/src/SheetMe.Data/Stores/PatientExtraStore.cs index b9421b1..f532b9f 100644 --- a/src/SheetMe.Data/Stores/PatientExtraStore.cs +++ b/src/SheetMe.Data/Stores/PatientExtraStore.cs @@ -614,6 +614,62 @@ public sealed class PatientExtraStore .ToArray(); } + /// + /// 적용 진료과 이름(OCM_적용진료과, :5659) — 통합 대표행의 AdpDepCod/AdpDtm 은 + /// 진료행(CodDepCod)과 적용일시의 별칭이다(dtHISOperatingInfo.vb:3699). + /// 원문은 적용일시 12자리 전체를 8자리 유효기간과 비교한다(절단 없음) — 그대로. + /// + public string AppliedDepartmentName(string depCod, string adpDtm) + => depCod.Trim().Length == 0 || adpDtm.Trim().Length == 0 ? string.Empty + : Scalar( + "SELECT v FROM (SELECT DepKorNam v FROM M_DepMst" + + " WHERE TRIM(DepCod) = :d AND :y BETWEEN DepStrDte AND DepEndDte)" + + " WHERE ROWNUM = 1", + ("d", depCod.Trim()), ("y", adpDtm.Trim())); + + /// + /// 전과 목록(OCM_전과, :12924 — GetCodInfDT(With_Join, 전체) 대응, dtCommonLib.vb:2777-2832). + /// 원문의 이름 조인(오늘 기준 유효기간, DepWrkTyp IN ('D','A') — SYBS·NYHY 의 'B' 추가는 + /// 미이식)과 정렬(CodStrDtm 오름차순) 그대로. 원문이 DefaultView 에 건 필터·정렬은 + /// 순회에 반영되지 않지만 SQL 이 이미 같은 조건·순서라 실질 무해했다. + /// + public IReadOnlyList> TransferList(decimal comNum) + => Rows( + "SELECT DepKorNam AS DepNam, Cod.CodDepCod, Cod.CodStrDtm, Cod.CodEndDtm FROM P_CodInf Cod" + + " LEFT OUTER JOIN M_UidMst Usr ON Usr.UidCod = CodDtrCod AND Usr.UidDtrYon = 'Y'" + + " AND TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN Usr.UidStrDte AND Usr.UidEndDte" + + " LEFT OUTER JOIN M_DepMst Dep ON DepCod = CodDepCod AND DepHspCod = Usr.UidHspCod" + + " AND DepWrkTyp IN ('D','A')" + + " AND TO_CHAR(SYSDATE, 'YYYYMMDD') BETWEEN DepStrDte AND DepEndDte" + + " WHERE CodComNum = :m AND CodMtiSeq = '0' ORDER BY CodStrDtm", + ("m", comNum)); + + /// + /// 간호정보조사지의 과거력 행(OCM_간호정보조사지_과거력 :13030 + GetNurseEmrDT :13523 + + /// GetNurseShtCod :13770) — 서식 우선순위(S113 성인 → S202 소아 → S163 산모, SRCH 갈래)로 + /// 고른 기록의 병력 항목 6칸을 PIVOT 한 행. 레거시는 서식 확인 3왕복 + 본조회였다 — + /// 우선순위를 CASE 정렬로 접어 한 왕복이다. 기록이 없으면 빈 사전. + /// + public IReadOnlyDictionary NurseHistoryRow(decimal comNum) + => Row( + "WITH Sht AS (SELECT v FROM (SELECT EmrShtCod v FROM E_EmrInf" + + " WHERE EmrComNum = :m AND EmrShtCod IN ('S113','S202','S163')" + + " AND (EmrSttFlg IS NULL OR EmrSttFlg <> 'D')" + + " ORDER BY CASE EmrShtCod WHEN 'S113' THEN 1 WHEN 'S202' THEN 2 ELSE 3 END)" + + " WHERE ROWNUM = 1)," + + " Data AS (SELECT SctObjNam, EmdRecord, ShtCod FROM E_EmrInf" + + " INNER JOIN E_ShtMst ON ShtCod = EmrShtCod" + + " INNER JOIN E_EmdInf ON EmdEmrKey = EmrKey" + + " INNER JOIN E_SctMst ON SctKey = EmdItmKey AND SctObjNam IN" + + " ('chkMedHis_1','chkMedHis_2','chkMedHis_3','chkMedHis_4','chkMedHis_5','txtMedHis')" + + " WHERE EmrComNum = :m AND EmrShtCod = (SELECT v FROM Sht)" + + " AND (EmrSttFlg IS NULL OR EmrSttFlg <> 'D'))" + + " SELECT * FROM (SELECT EmdRecord, SctObjNam, ShtCod FROM Data)" + + " PIVOT (MIN(EmdRecord) FOR SctObjNam IN ('chkMedHis_1' AS 당뇨, 'chkMedHis_2' AS 고혈압," + + " 'chkMedHis_3' AS 간염, 'chkMedHis_4' AS 결핵, 'chkMedHis_5' AS 병력기타," + + " 'txtMedHis' AS 병력기타내용))", + ("m", comNum)); + /// 스칼라 1칸 — 0행이거나 NULL 이면 빈 문자열. 잔여 공백은 걷는다(문맥 행과 같은 규칙) private string Scalar(string sql, params (string Name, object Value)[] binds) { diff --git a/src/SheetMe.Designer/Diagnostics/DbSmoke.cs b/src/SheetMe.Designer/Diagnostics/DbSmoke.cs index 288ee9c..6da4be5 100644 --- a/src/SheetMe.Designer/Diagnostics/DbSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/DbSmoke.cs @@ -2448,6 +2448,9 @@ public static class DbSmoke ("수술일자", () => owner2 is null ? "-" : extra.SurgeryDate(owner2.Value, false).Length.ToString()), ("외래실진료일", () => extra.OutpatientVisitDays(extraOwner, "999912312359", "05", false).Count.ToString()), ("청구과목", () => extra.BillingDepartmentName("0506").Length.ToString()), + ("적용진료과", () => extra.AppliedDepartmentName("0506", "202601011200").Length.ToString()), + ("전과목록", () => owner2 is null ? "-" : extra.TransferList(owner2.Value).Count.ToString()), + ("간호과거력", () => owner2 is null ? "-" : extra.NurseHistoryRow(owner2.Value).Count.ToString()), }; var shapes2 = new List(); var failed = new List(); diff --git a/src/SheetMe.Designer/Services/PatientTagResolver.cs b/src/SheetMe.Designer/Services/PatientTagResolver.cs index 06939e3..d45caf8 100644 --- a/src/SheetMe.Designer/Services/PatientTagResolver.cs +++ b/src/SheetMe.Designer/Services/PatientTagResolver.cs @@ -1505,6 +1505,7 @@ public sealed class PatientTagResolver : ITagValueResolver "OCM_당뇨식칼로리_List", "OCM_수술일자", "OCM_수술일자_마지막수술", "OCM_외래실진료일", "OCM_외래실진료일_과목별", "OCM_외래실진료일수", "ETC_승인의사", "OCM_수술소독간호사", + "OCM_적용진료과", "OCM_적용진료의사", "OCM_전과", "OCM_간호정보조사지_과거력", }; /// @@ -1943,6 +1944,98 @@ public sealed class PatientTagResolver : ITagValueResolver // PURME 전용(:3028) — 그 밖의 병원은 빈 SQL 을 실행해 항상 빈 값이다 return new TagValue(false, "PURME 전용 태그입니다 — 그 밖의 병원에서는 레거시도 항상 빈 값입니다"); + case "OCM_적용진료과": + { + // AdpDepCod/AdpDtm 은 문맥 진료행·적용일시의 별칭이다(:5659, + // dtHISOperatingInfo.vb:3699). 원문은 12자리 적용일시를 그대로 비교한다. + var depCod = PatientContext.Value(context.CodInf, "CodDepCod"); + return depCod.Length == 0 + ? new TagValue(false, "이 내원의 진료과 코드를 읽지 못했습니다") + : Wrap(store.AppliedDepartmentName(depCod, context.AdpDtm), + "적용일시에 유효한 진료과 마스터 행이 없습니다"); + } + case "OCM_적용진료의사": + { + // GetUidMst(AdpDtrCod, 적용일 8자) → UidNam(:3939-3949) + var dtrCod = PatientContext.Value(context.CodInf, "CodDtrCod"); + if (dtrCod.Length == 0) + { + return new TagValue(false, "이 내원의 담당의 코드를 읽지 못했습니다"); + } + if (contextStoreSource?.Invoke() is not { } ctxStore) + { + return new TagValue(false, "진료과 시점 조회가 연결되지 않았습니다"); + } + var name = ctxStore.UserNameAt(dtrCod, context.AdpDtm); + return name.Length > 0 + ? new TagValue(true, name) + : new TagValue(false, "적용일에 유효한 사용자 마스터 행이 없습니다"); + } + case "OCM_전과": + { + var comNum = PatientContext.Value(context.ComInf, "ComNum"); + if (!decimal.TryParse(comNum, out var m)) + { + return new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다"); + } + var rows = store.TransferList(m); + if (rows.Count <= 1) + { + // 레거시는 행이 2개 이상일 때만 조립한다(:12940) — 전과가 없다는 뜻 + return new TagValue(false, "전과 이력이 없습니다(진료 행이 1개 이하)"); + } + // 원문 조립(:12944-12977): 과가 직전 행과 다를 때만 "과명 / 시작 ~ 끝" 을 + // AppendLine — 마지막 줄 뒤에도 개행이 남는다. 무기한(2999-12-31) 판정은 + // 원문의 Substring(1,8) 결함으로 절대 참이 되지 않아 실제 값이 그대로 + // 찍힌다 — 그대로 옮겼다(판정 자체를 하지 않는다). + var text = new System.Text.StringBuilder(); + for (var i = 0; i < rows.Count; i++) + { + var dep = PatientContext.Value(rows[i], "CodDepCod"); + if (i > 0 && dep == PatientContext.Value(rows[i - 1], "CodDepCod")) + { + continue; + } + var depNam = PatientContext.Value(rows[i], "DepNam"); + var from = Stamp(PatientContext.Value(rows[i], "CodStrDtm")); + var to = Stamp(PatientContext.Value(rows[i], "CodEndDtm")); + if (depNam.Length > 0 && from.Length > 0 && to.Length > 0) + { + text.Append($"{depNam} / {from} ~ {to}\r\n"); + } + } + return text.Length > 0 + ? new TagValue(true, text.ToString()) + : new TagValue(false, "조립할 전과 줄이 없습니다(과명 또는 구간이 비어 있음)"); + } + case "OCM_간호정보조사지_과거력": + { + var comNum = PatientContext.Value(context.ComInf, "ComNum"); + if (!decimal.TryParse(comNum, out var m)) + { + return new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다"); + } + var row = store.NurseHistoryRow(m); + if (row.Count == 0) + { + return new TagValue(false, + "간호정보조사지 기록(S113·S202·S163)이 없습니다"); + } + // 원문 조립(:13049-13092): 체크된 병력을 " ," 로 잇고(공백+콤마 — 원문 + // 그대로), 기타는 리터럴 "기타". 마지막에 개행 + 기타내용 — 병력이 하나도 + // 없어도 행만 있으면 개행은 붙는다. + var kinds = string.Empty; + foreach (var (column, label) in new[] + { ("당뇨", "당뇨"), ("고혈압", "고혈압"), ("간염", "간염"), + ("결핵", "결핵"), ("병력기타", "기타") }) + { + if (PatientContext.Value(row, column).Length > 0) + { + kinds = kinds.Length > 0 ? $"{kinds} ,{label}" : label; + } + } + return new TagValue(true, $"{kinds}\r\n{PatientContext.Value(row, "병력기타내용")}"); + } case "OCM_전과내역": { var comNum = PatientContext.Value(context.ComInf, "ComNum"); @@ -2155,6 +2248,7 @@ public sealed class PatientTagResolver : ITagValueResolver "OCM_당뇨식칼로리_List", "OCM_수술일자", "OCM_수술일자_마지막수술", "OCM_외래실진료일", "OCM_외래실진료일_과목별", "OCM_외래실진료일수", "ETC_승인의사", "OCM_수술소독간호사", + "OCM_적용진료과", "OCM_적용진료의사", "OCM_전과", "OCM_간호정보조사지_과거력", "OCM_입원시간", "OCM_입원일시", "OCM_입원일시_영문", "OCM_입실시간", "OCM_의사퇴원예고일시", "OCM_퇴원예정일시", "OCM_퇴원시간", "OCM_퇴원일시", "OCM_퇴원예고일시", "OCM_재원일수", "OCM_퇴원예고재원일수", "OCM_입원일자_낮병동",