유형·수납·진료기간·응급증상·전과내역 8종 — 185종 → 193종

OCM_계산유형(bzDataInterface.vb:5133 — 문맥 자격의 보험·계산 코드로
M_CalMst.CalNam), OCM_보험유형(:5186 — M_InsMst.InsNam),
OCM_수납일시(:5287 — 자격의 수납청구→수납 갱신일시. 원문 두 쿼리 다
비결정 Rows(0) — 최신 청구의 최신 수납으로 고정, 한 왕복으로 접음),
OCM_진료기간(:2236 — SRCH 갈래: 퇴원이면 접수~퇴원, 재원이면 최신
퇴원약 오더→퇴원예정→오늘 순. 외래: 최초 접수~최신 접수/오늘.
HANKOOK·SRH·GUPOSS 분기 미이식), OCM_진료기간_진료과(:2575 — 입원만
값. 외래는 두 번째 쿼리의 진료과 바인드 미등록(레거시 결함, :2644)으로
상시 ORA-01008→빈 값 — 사유로 완결), OCM_응급증상여부_YES/NO
(:12748/:12830 — 응급의료관리료 오더의 산정 여부. 코드 목록은
GetDtlMst("EMGEMGCOD") 그대로 + 0건이면 하드코딩 6종 폴백.
행이 없으면 레거시 $NoCheck$ — 체크 상태를 정하지 않는다.
SJRCH 분기 미이식), OCM_전과내역(:1103 — 과명+공백7+구간, CRLF 결합.
과 조인이 의사 조인에 종속되는 원문 모양 유지).

체크류 렌더 보완: 값을 못 만든 태그(사유)는 체크박스·라디오의 컨트롤을
그대로 둔다 — 레거시도 SetValue 를 건너뛴다. 라벨에 사유를 쓰면
"남 ☑" 이 "[태그 — 사유] ☑" 가 되어 서식이 깨진다.

- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 193종)
- --db-patient ①~㉟ 전건 통과 (㉟ 에 7종 추가 — 보험유형 실값 4자,
  응급 쿼리의 ORDER BY ROWID 모호성은 OdrKey 타이브레이크로 수정)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-19 19:16:37 +09:00
co-authored by Claude Fable 5
parent 7bae9b9057
commit 4e6e086ece
4 changed files with 314 additions and 0 deletions
@@ -1380,6 +1380,9 @@ public sealed class PatientTagResolver : ITagValueResolver
"OCM_감염분류_감염정보", "OCM_감염정보",
"OCM_퇴원분석_수술처치명", "OCM_Cosign_의사명",
"OCM_병실인실", "OCM_병실차액",
"OCM_계산유형", "OCM_보험유형", "OCM_수납일시",
"OCM_진료기간", "OCM_진료기간_진료과",
"OCM_응급증상여부_YES", "OCM_응급증상여부_NO", "OCM_전과내역",
};
/// <summary>
@@ -1638,6 +1641,160 @@ public sealed class PatientTagResolver : ITagValueResolver
? Wrap(store.RoomExtraCharge(m), "병실 등급·산정 마스터가 짝이 되는 행이 없습니다")
: new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
}
case "OCM_계산유형":
case "OCM_보험유형":
case "OCM_수납일시":
{
// 자격의 코드·시작일은 문맥 CoiInf 행 — 레거시의 시점 분기
// (퇴원/현재/접수, :5148-5156)와 같은 판정이다.
// 레거시는 자격 0행이면 Rows(0) 예외 → 오류창 → "" 였다.
if (context.CoiInf.Count == 0)
{
return new TagValue(false, "이 내원에 보험 자격(P_CoiInf) 행이 없습니다");
}
var insCod = PatientContext.Value(context.CoiInf, "CoiInsCod");
var strDte = PatientContext.Value(context.CoiInf, "CoiStrDte");
if (tag == "OCM_계산유형")
{
return Wrap(store.CalculationTypeName(insCod,
PatientContext.Value(context.CoiInf, "CoiCalCod"), strDte),
"계산 마스터(M_CalMst)에 짝이 되는 행이 없습니다");
}
if (tag == "OCM_보험유형")
{
return Wrap(store.InsuranceTypeName(insCod, strDte),
"보험 마스터(M_InsMst)에 짝이 되는 행이 없습니다");
}
var comNum = PatientContext.Value(context.ComInf, "ComNum");
if (!decimal.TryParse(comNum, out var m))
{
return new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
}
var stamp = store.ReceiptStamp(m,
PatientContext.Value(context.CoiInf, "CoiMtiSeq"), strDte);
return stamp.Length >= 12
? new TagValue(true, Stamp(stamp))
: new TagValue(false, "이 자격의 수납(P_RcpInf) 기록이 없습니다");
}
case "OCM_진료기간":
{
// SRCH 갈래(:2295-2323)와 외래 공통 갈래(:2325-2380)를 옮겼다.
// HANKOOK·SRH·GUPOSS·Case Else 분기는 우리 병원이 아니라 미이식.
var acp = PatientContext.Value(context.ComInf, "ComAcpDtm");
if (acp.Length < 8)
{
return new TagValue(false, "이 내원의 접수일시를 읽지 못했습니다");
}
var start = Hyphenate(acp[..8]);
var today = PatientContext.Value(hospital, "ServerToday");
if (Com(context, "ComPatTyp") == "I")
{
if (Com(context, "ComPrgStt") == "ILV")
{
var lev = Com(context, "ComLevDtm");
return new TagValue(true, $"{start} ~ {Hyphenate(Head(lev, 8))}");
}
var comNum = PatientContext.Value(context.ComInf, "ComNum");
if (!decimal.TryParse(comNum, out var m))
{
return new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
}
// 재원 중 — 끝은 최신 퇴원약 오더일 → 퇴원예정일 → 오늘 순(:2299-2321)
var order = store.DischargeMedicationOrderDate(m);
var end = order.Length >= 8 ? order[..8]
: Com(context, "ComLevSch").Length >= 8 ? Com(context, "ComLevSch")[..8]
: today;
return end.Length >= 8
? new TagValue(true, $"{start} ~ {Hyphenate(end[..8])}")
: new TagValue(false, "요양기관 행(서버 오늘)을 읽지 못했습니다");
}
// 외래 — 최초 접수 ~ (그 이후 최신 접수 또는 오늘)
var first = store.TreatmentPeriodStart(chtNum, acp);
if (first.Length < 8)
{
return new TagValue(false, "취소 아닌 이전 내원이 없습니다");
}
var last = store.TreatmentPeriodEnd(chtNum, first);
var tail = last.Length >= 8 ? last[..8] : today;
return tail.Length >= 8
? new TagValue(true, $"{Hyphenate(first[..8])} ~ {Hyphenate(tail[..8])}")
: new TagValue(false, "요양기관 행(서버 오늘)을 읽지 못했습니다");
}
case "OCM_진료기간_진료과":
{
// 입원 갈래(:2600-2610)만 값이 된다. 외래 갈래는 두 번째 쿼리의
// 진료과 바인드(sDepCod)가 파라미터에 등록되지 않아(레거시 결함,
// :2644-2647) 항상 ORA-01008 → 오류창 → 빈 값이었다 — 사유로 말한다.
if (Com(context, "ComPatTyp") != "I")
{
return new TagValue(false,
"레거시 결함(진료과 바인드 미등록)으로 외래에서는 항상 빈 값인 태그입니다");
}
var acp = PatientContext.Value(context.ComInf, "ComAcpDtm");
if (acp.Length < 8)
{
return new TagValue(false, "이 내원의 접수일시를 읽지 못했습니다");
}
var start = Hyphenate(acp[..8]);
if (Com(context, "ComPrgStt") == "ILV")
{
return new TagValue(true,
$"{start} ~ {Hyphenate(Head(Com(context, "ComLevDtm"), 8))}");
}
var today = PatientContext.Value(hospital, "ServerToday");
return today.Length >= 8
? new TagValue(true, $"{start} ~ {Hyphenate(today)}")
: new TagValue(false, "요양기관 행(서버 오늘)을 읽지 못했습니다");
}
case "OCM_응급증상여부_YES":
case "OCM_응급증상여부_NO":
{
// 체크박스 태그다: "Y"=체크, ""=언체크, 행 없음=상태를 정하지 않음
// (레거시 $NoCheck$ — 사유로 돌려주면 체크류 렌더가 컨트롤을 그대로 둔다).
// SJRCH 분기(:12878-12886)는 미이식 — Case Else 갈래만 옮겼다.
var comNum = PatientContext.Value(context.ComInf, "ComNum");
if (!decimal.TryParse(comNum, out var m))
{
return new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
}
var insYon = store.EmergencySymptomInsYon(m);
if (insYon is null)
{
return new TagValue(false,
"응급의료관리료 오더가 없어 체크 여부를 정하지 않습니다");
}
var yes = insYon == "0";
return new TagValue(true,
(tag == "OCM_응급증상여부_YES") == yes ? "Y" : string.Empty);
}
case "OCM_전과내역":
{
var comNum = PatientContext.Value(context.ComInf, "ComNum");
if (!decimal.TryParse(comNum, out var m))
{
return new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
}
var history = store.TransferHistory(m);
if (history.Count == 0)
{
return new TagValue(false, "진료 행(P_CodInf) 이력이 없습니다");
}
// 원문 결합(:1140-1150): 과명 + 공백 7 + 시작 ~ 끝, 행 간 CRLF(끝에는 없음)
var text = new System.Text.StringBuilder();
foreach (var row in history)
{
if (text.Length > 0)
{
text.Append("\r\n");
}
text.Append(PatientContext.Value(row, "DepNam"))
.Append(" ")
.Append(Stamp(PatientContext.Value(row, "CodStrDtm")))
.Append(" ~ ")
.Append(Stamp(PatientContext.Value(row, "CodEndDtm")));
}
return new TagValue(true, text.ToString());
}
case "OCM_LMP":
{
var comNum = PatientContext.Value(context.ComInf, "ComNum");
@@ -1814,6 +1971,8 @@ public sealed class PatientTagResolver : ITagValueResolver
"ETC_건강보험증번호", "ETC_재해발생일", "ETC_산재지정번호",
"OCM_감염분류_감염정보", "OCM_감염정보", "OCM_퇴원분석_수술처치명", "OCM_Cosign_의사명",
"OCM_병실인실", "OCM_병실차액", "OCM_진료과_전화번호", "OCM_전과일시", "OCM_전과일자",
"OCM_계산유형", "OCM_보험유형", "OCM_수납일시", "OCM_진료기간", "OCM_진료기간_진료과",
"OCM_응급증상여부_YES", "OCM_응급증상여부_NO", "OCM_전과내역",
"OCM_입원시간", "OCM_입원일시", "OCM_입원일시_영문", "OCM_입실시간",
"OCM_의사퇴원예고일시", "OCM_퇴원예정일시", "OCM_퇴원시간", "OCM_퇴원일시",
"OCM_퇴원예고일시", "OCM_재원일수", "OCM_퇴원예고재원일수", "OCM_입원일자_낮병동",
@@ -196,6 +196,13 @@ public static class PrintService
{
return control;
}
// 체크류에서 값을 못 만들면 컨트롤을 그대로 둔다 — 레거시도 SetValue 를 건너뛰고
// ($NoCheck$ · 오류) 라벨과 기본 체크 상태를 유지한다. 라벨에 사유를 쓰면
// "남 ☑" 이 "[태그 — 사유] ☑" 가 되어 서식이 깨진다.
if (!value.Resolved && control is CheckBoxViewModel or RadioButtonViewModel)
{
return control;
}
var model = control.Model.Clone();
if (value.Resolved && control is CheckBoxViewModel or RadioButtonViewModel)
{