입·퇴원 일시와 재원일수 13종 — 131종 → 144종
FromStay 12종(bzDataInterface.vb:7152-7940, 전부 Case Else 갈래):
입원시간(HH:MM)·입원일시(이름과 달리 날짜만 — SJRCH·BSYD 만 시각
포함)·입원일시_영문·입실시간(ComTrsDtm 슬래시 12자)·의사퇴원예고일시
(ComSchLev)·퇴원예정일시(ComLevSch)·퇴원시간·퇴원일시·퇴원예고일시
(ILV→ComLevDtm / ILP→ComLevSch)·재원일수·퇴원예고재원일수
(DateDiff(Day)+1). FromExtra 에 입원일자_낮병동(:7260 — P_DcpInf
구간 매칭, 시작일 최신 행 고정).
MsgBox 갈래("퇴원일자를 현재로 설정하시겠습니까?")는 조회 중 대화
상자라 옮기지 않고 사유로 말한다. 그 질문을 건너뛰는 개원일 분기
(HspStrDte>=20200901 → 빈, 외래 <20190501 → 오늘)는 그대로 옮겼다 —
개원일과 서버 오늘을 요양기관 행에 컬럼 2개로 추가(HspStrDte,
ServerToday). 재원일수의 HspCod >= "KBJY" 는 등호가 아닌 크기비교
(레거시 오타로 보임) — MsgBox 갈래라 사유로 흡수된다.
- dotnet test 338/338 · --edit-smoke 실패 0 (이름 검사 144종)
- --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
ec47c98bdd
commit
975aff4211
@@ -218,9 +218,12 @@ public sealed class PatientContextStore
|
||||
}
|
||||
using var connection = new OracleConnection(connectionString);
|
||||
connection.Open();
|
||||
// HspStrDte(개원일)는 퇴원 계열 태그의 분기 재료(HspCfg.HspStrDte 대응),
|
||||
// ServerToday 는 "오늘" 폴백·재원일수 계산용 서버 날짜 — 단말 시계를 쓰지 않는다
|
||||
return Row(connection,
|
||||
"SELECT * FROM (SELECT HspInsNam, HspOwnNam, HspTelNum, HspFaxNum,"
|
||||
+ " HspKorAdr, HspEngNam, HspEngAdr, HspRgnNum, HspInsNum FROM M_DepMst"
|
||||
+ " HspKorAdr, HspEngNam, HspEngAdr, HspRgnNum, HspInsNum, HspStrDte,"
|
||||
+ " TO_CHAR(SYSDATE, 'YYYYMMDD') AS ServerToday FROM M_DepMst"
|
||||
+ " INNER JOIN M_HspMst ON DepHspCod = HspCod AND :y BETWEEN HspStrDte AND HspEndDte"
|
||||
+ " WHERE TRIM(DepCod) = :d AND :y BETWEEN DepStrDte AND DepEndDte) WHERE ROWNUM = 1",
|
||||
("d", code), ("y", date[..8]));
|
||||
|
||||
@@ -181,6 +181,21 @@ public sealed class PatientExtraStore
|
||||
+ " ORDER BY ComAcpDtm) WHERE ROWNUM = 1",
|
||||
("c", chtNum), ("a", acpDtm), ("p", depCod2));
|
||||
|
||||
/// <summary>
|
||||
/// 낮병동 입원일자(OCM_입원일자_낮병동, :7260) — 이 내원의 접수일시를 포함하는
|
||||
/// 낮병동 신청(P_DcpInf, DcpAcpStt='A') 구간의 시작일. 원문 Rows(0) 은 구간이
|
||||
/// 겹치면 임의 행이었다 — 시작일 최신 행으로 고정한다.
|
||||
/// </summary>
|
||||
public string DayCareAdmissionDate(decimal comNum)
|
||||
=> Scalar(
|
||||
"SELECT v FROM (SELECT DcpStrDte v FROM P_ComInf"
|
||||
+ " INNER JOIN P_DcpInf ON ComChtNum = DcpChtNum"
|
||||
+ " AND ComAcpDtm BETWEEN DcpStrDte AND DcpEndDte || '2359'"
|
||||
+ " AND DcpAcpStt = 'A'"
|
||||
+ " WHERE ComNum = :m AND ComAcpStt <> 'OC' AND ComDayCar = 'Y'"
|
||||
+ " ORDER BY DcpStrDte DESC) WHERE ROWNUM = 1",
|
||||
("m", comNum));
|
||||
|
||||
/// <summary>스칼라 1칸 — 0행이거나 NULL 이면 빈 문자열. 잔여 공백은 걷는다(문맥 행과 같은 규칙)</summary>
|
||||
private string Scalar(string sql, params (string Name, object Value)[] binds)
|
||||
{
|
||||
|
||||
@@ -315,6 +315,10 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
{
|
||||
return extraValue;
|
||||
}
|
||||
if (FromStay(tag) is { } stayValue)
|
||||
{
|
||||
return stayValue;
|
||||
}
|
||||
if (Computed.TryGetValue(tag, out var compute))
|
||||
{
|
||||
var made = compute(context);
|
||||
@@ -891,6 +895,120 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
: new TagValue(false, "수술일자 값이 형식에 맞지 않습니다");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 입·퇴원 일시와 재원일수 12종(bzDataInterface.vb:7152-7940, 전부 Case Else 갈래).
|
||||
///
|
||||
/// <b>MsgBox 갈래는 옮기지 않는다.</b> 레거시는 퇴원 전 환자에서 "퇴원일자를 현재로
|
||||
/// 설정하시겠습니까?"를 조회 도중에 묻는다 — 조회 함수의 대화 상자는 부작용이므로
|
||||
/// 사유로 말한다. 다만 개원일(HspStrDte) 분기로 그 질문 자체를 건너뛰는 병원이
|
||||
/// 있어(:7573 — 2020-09-01 이후 개원이면 무조건 "") 그 분기는 그대로 옮겼다.
|
||||
/// 재원일수의 <c>HspCod >= "KBJY"</c> 는 등호가 아니라 <b>크기비교</b>(레거시 오타로
|
||||
/// 보인다) — 그 갈래도 MsgBox 라 사유로 흡수되므로 따로 흉내내지 않는다.
|
||||
/// </summary>
|
||||
private TagValue? FromStay(string tag)
|
||||
{
|
||||
if (tag is not ("OCM_입원시간" or "OCM_입원일시" or "OCM_입원일시_영문" or "OCM_입실시간"
|
||||
or "OCM_의사퇴원예고일시" or "OCM_퇴원예정일시" or "OCM_퇴원시간" or "OCM_퇴원일시"
|
||||
or "OCM_퇴원예고일시" or "OCM_재원일수" or "OCM_퇴원예고재원일수"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var inpatient = Com(context, "ComPatTyp") == "I";
|
||||
var acp = Com(context, "ComAcpDtm");
|
||||
var prg = Com(context, "ComPrgStt");
|
||||
var lev = Com(context, "ComLevDtm");
|
||||
var sch = Com(context, "ComLevSch");
|
||||
var openDate = PatientContext.Value(hospital, "HspStrDte");
|
||||
var today = PatientContext.Value(hospital, "ServerToday");
|
||||
|
||||
// 레거시가 사용자에게 묻는 자리(:7580 등) — 자동 값을 만들지 않는다
|
||||
static TagValue Ask() => new(false,
|
||||
"레거시는 퇴원 전 환자에서 이 값을 사용자에게 물어 정합니다 — 자동으로 만들지 않습니다");
|
||||
// 개원일·서버 오늘이 필요한 갈래에서 요양기관 행이 없으면 판정을 못 한다
|
||||
TagValue? NeedFacility() => openDate.Length == 0 || today.Length == 0
|
||||
? new TagValue(false, "요양기관 행(개원일)을 읽지 못해 퇴원 전 분기를 판정할 수 없습니다")
|
||||
: null;
|
||||
|
||||
switch (tag)
|
||||
{
|
||||
case "OCM_입원시간":
|
||||
return Ok(inpatient ? Time4(acp) : string.Empty);
|
||||
case "OCM_입원일시":
|
||||
// 이름과 달리 날짜만 나온다(:7203 — SJRCH·BSYD 만 시각 포함, 여기는 Else)
|
||||
return Ok(inpatient ? Hyphenate(Head(acp, 8)) : string.Empty);
|
||||
case "OCM_입원일시_영문":
|
||||
return Ok(inpatient ? Reorder(Head(acp, 8), 4, 6, 0) : string.Empty);
|
||||
case "OCM_입실시간":
|
||||
return Ok(SlashStamp(Com(context, "ComTrsDtm")));
|
||||
case "OCM_의사퇴원예고일시":
|
||||
return Ok(inpatient && Com(context, "ComSchLev").Length > 0
|
||||
? Stamp(Com(context, "ComSchLev")) : string.Empty);
|
||||
case "OCM_퇴원예정일시":
|
||||
return Ok(inpatient && sch.Length > 0 ? Stamp(sch) : string.Empty);
|
||||
case "OCM_퇴원시간":
|
||||
return prg == "ILV" ? Ok(Time4(lev))
|
||||
: inpatient ? Ask()
|
||||
: Ok(string.Empty);
|
||||
case "OCM_퇴원일시":
|
||||
return prg == "ILV" ? Ok(Hyphenate(Head(lev, 8)))
|
||||
: inpatient
|
||||
? NeedFacility() ?? (string.CompareOrdinal(openDate, "20200901") >= 0
|
||||
? Ok(string.Empty) : Ask())
|
||||
: NeedFacility() ?? Ok(string.CompareOrdinal(openDate, "20190501") < 0
|
||||
? Hyphenate(today) : string.Empty);
|
||||
case "OCM_퇴원예고일시":
|
||||
return prg == "ILV" ? Ok(Hyphenate(Head(lev, 8)))
|
||||
: prg == "ILP" ? Ok(Hyphenate(Head(sch, 8)))
|
||||
: inpatient
|
||||
? NeedFacility() ?? (string.CompareOrdinal(openDate, "20200901") >= 0
|
||||
? Ok(string.Empty) : Ask())
|
||||
: NeedFacility() ?? Ok(string.CompareOrdinal(openDate, "20190501") < 0
|
||||
? Hyphenate(today) : string.Empty);
|
||||
case "OCM_재원일수":
|
||||
return prg == "ILV" ? StayDays(acp, lev)
|
||||
: inpatient
|
||||
? NeedFacility() ?? (string.CompareOrdinal(openDate, "20200901") >= 0
|
||||
? StayDays(acp, today) : Ask())
|
||||
: NeedFacility() ?? StayDays(acp, today);
|
||||
default: // OCM_퇴원예고재원일수
|
||||
return prg == "ILV" ? StayDays(acp, lev)
|
||||
: prg == "ILP" ? StayDays(acp, sch)
|
||||
: inpatient
|
||||
? NeedFacility() ?? (string.CompareOrdinal(openDate, "20200901") >= 0
|
||||
? Ok(string.Empty) : Ask())
|
||||
: NeedFacility() ?? StayDays(acp, today);
|
||||
}
|
||||
|
||||
static TagValue Ok(string value) => new(true, value);
|
||||
}
|
||||
|
||||
/// <summary>YYYYMMDDHHMM 의 시각만 — "HH:MM". 12자리가 안 되면 빈 값</summary>
|
||||
private static string Time4(string value)
|
||||
=> value.Length >= 12 ? $"{value[8..10]}:{value[10..12]}" : string.Empty;
|
||||
|
||||
/// <summary>YYYYMMDDHHMM → "YYYY/MM/DD HH:MM". 12자리가 안 되면 빈 값</summary>
|
||||
private static string SlashStamp(string value)
|
||||
=> value.Length >= 12
|
||||
? $"{value[..4]}/{value[4..6]}/{value[6..8]} {value[8..10]}:{value[10..12]}"
|
||||
: string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 재원일수 — (끝 날짜 - 시작 날짜) + 1 (레거시 DateDiff(Day)+1).
|
||||
/// 날짜를 못 읽으면 레거시는 예외→"" 였다 — 여기서는 사유로 말한다.
|
||||
/// </summary>
|
||||
private static TagValue StayDays(string from, string to)
|
||||
{
|
||||
if (from.Length < 8 || to.Length < 8
|
||||
|| !DateTime.TryParseExact(from[..8], "yyyyMMdd", null,
|
||||
System.Globalization.DateTimeStyles.None, out var start)
|
||||
|| !DateTime.TryParseExact(to[..8], "yyyyMMdd", null,
|
||||
System.Globalization.DateTimeStyles.None, out var end))
|
||||
{
|
||||
return new TagValue(false, "재원일수를 만들 날짜를 읽지 못했습니다");
|
||||
}
|
||||
return new TagValue(true, ((end - start).Days + 1).ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 환자 부가정보 7종 — 태그별 자기 SQL(<see cref="PatientExtraStore"/>).
|
||||
/// 전부 지연 + 태그별 캐시다: 이 태그가 없는 서식이 대부분이라 미리 당기지 않고,
|
||||
@@ -901,7 +1019,8 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
if (tag is not ("PAT_실제생년월일" or "PAT_국적" or "PAT_장애등급" or "PAT_건보세대주명"
|
||||
or "PAT_건보_급여_세대주명" or "PAT_협력업체" or "PAT_보호자연락처"
|
||||
or "OCM_FollowUp" or "OCM_외출외박신청일" or "OCM_외출외박종료일"
|
||||
or "OCM_최초내원일" or "OCM_초진일_발병일" or "OCM_신환_초진일"))
|
||||
or "OCM_최초내원일" or "OCM_초진일_발병일" or "OCM_신환_초진일"
|
||||
or "OCM_입원일자_낮병동"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -986,6 +1105,18 @@ public sealed class PatientTagResolver : ITagValueResolver
|
||||
}
|
||||
case "PAT_보호자연락처":
|
||||
return Wrap(store.GuardianPhone(chtNum), "부가정보에 보호자연락처가 없습니다");
|
||||
case "OCM_입원일자_낮병동":
|
||||
{
|
||||
var comNum = PatientContext.Value(context.ComInf, "ComNum");
|
||||
if (!decimal.TryParse(comNum, out var m))
|
||||
{
|
||||
return new TagValue(false, "이 내원의 내원번호를 읽지 못했습니다");
|
||||
}
|
||||
var day = store.DayCareAdmissionDate(m);
|
||||
return day.Length >= 8
|
||||
? new TagValue(true, Hyphenate(day[..8]))
|
||||
: new TagValue(false, "낮병동 신청(P_DcpInf) 구간이 이 내원을 포함하지 않습니다");
|
||||
}
|
||||
case "OCM_FollowUp":
|
||||
{
|
||||
// 지금보다 뒤의 내원이 없으면 레거시도 "" 다(:1055) — 사유로 말한다
|
||||
@@ -1144,6 +1275,9 @@ 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_입원일자_낮병동",
|
||||
"PAT_우편번호", "PAT_도로명주소", "PAT_지번주소", "PAT_영문도로명주소",
|
||||
"OCM_담당의사", "OCM_담당의사_담당의사코드", "OCM_담당의사_연락처",
|
||||
"OCM_진료과", "ETC_요양기관명칭_병원명",
|
||||
|
||||
Reference in New Issue
Block a user