conflict 6건을 고치고 재료 재사용으로 10종을 더한다 — 30종 → 40종

## conflict 6건 (통합 계획의 "고칠 것")

값이 바뀌는 것 둘:
- ETC_현재년도 — "2026" → "2026년"(bzDataInterface.vb:16923 의 + "년").
  형제 ETC_현재일시_1_년 은 접미가 없어 둘을 갈랐다(ClockFormat.YearSuffixed 신설).
  "2026" 을 단정하던 테스트도 고쳤다.
- ETC_로그인_사용자ID_사용자명 — 레거시(:17930)는 빈 값 검사 없이 항상 결합한다.
  한쪽이 비어도 "U01." 이 그대로 나간다. 조건을 붙였던 것이 레거시와 달랐다.

행 선택이 바뀌는 것 둘:
- ETC_로그인_사용자연락처 / 원내연락처 — 전에는 M_UidMst 를 조건 없이 첫 행.
  이력이 여러 행인 사용자에서 옛 연락처가 나올 수 있었다.
  유효기간(SYSDATE 일자 BETWEEN)으로 고르고, 0행이면 레거시 폴백(:10641)처럼
  조건 없이 다시 찾는다.

의도적 차이로 기록한 것 둘(코드 주석):
- 유효기간 판정 일자 — 레거시는 로그인 시점, 여기는 SYSDATE(자정 넘긴 세션에서만 갈림).
- Trim — 레거시는 무가공, 여기는 걷는다(CHAR 잔여 공백이 종이에 찍히는 쪽이 더 이상하다).

## new 48종 중 재료 재사용으로 되는 10종

워크플로 매핑 상세(docs/new48-mappings.json)를 붙여 보니 46종이 needs-lookup 인데,
그중 10종은 <b>이미 든 행의 다른 컬럼</b>이거나 문맥 계산이었다:

- 담당의 행 재사용 3: OCM_담당의사_영문(UidEngNam) · 면허번호(UidLicNum) · 전문의번호(UidSpcLic)
  — 조회 갈래가 OCM_담당의사와 문자 그대로 같고 집는 컬럼만 다르다(:4094·4119·4144)
- CowInf 재사용 3: OCM_병실베드(CowBedCod) · 병동_병실 · 병동_병실_베드(" / " 결합, 행 없으면 "")
- 문맥 계산 1: OCM_내원유형(입원/응급실/외래 — ComPatTyp·ComEmgYon)
- User 계열 3: OCM_User진료과 · _한글명칭 · User진료의사명.
  로그인 사용자가 의사(UidDtrYon=Y)면 사용자의 과·이름, 아니면 환자의 것.
  HisUser 에 DoctorYn·DepOfficialName 을 추가했다(세션 조회 SELECT 확장).

남은 new 는 새 조회가 필요한 세 무리다: 상병(Okd_상병쿼리) · 수술(GetOprInfDT) ·
심사(Bkd_상병쿼리). 다음 배치.

## 게이트

- dotnet test 338/338 (현재년도·ID.사용자명 판정 갱신)
- --edit-smoke 실패 0 (이름 검사 40종)
- --db-patient ①~㉔ 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-19 15:05:38 +09:00
co-authored by Claude Opus 5
parent e62e43d176
commit 9278b39a6d
7 changed files with 813 additions and 13 deletions
@@ -87,6 +87,23 @@ public sealed class PatientTagResolver : ITagValueResolver
// 외래는 CowInf 가 0행이라 자연히 빈 값이 된다(레거시도 외래·비낮병동은 "" 다).
["OCM_병동"] = c => PatientContext.Value(c.CowInf, "CowWadCod"),
["OCM_병실"] = c => PatientContext.Value(c.CowInf, "CowRomCod"),
["OCM_병실베드"] = c => PatientContext.Value(c.CowInf, "CowBedCod"),
// 조합형 — 구분자는 " / " (양쪽 공백, 레거시 그대로). 행이 없으면 빈 값이지
// " / " 만 남기지 않는다(레거시는 rows>0 안에서만 결합한다).
["OCM_병동_병실"] = c => c.CowInf.Count == 0 ? string.Empty
: $"{PatientContext.Value(c.CowInf, "CowWadCod")} / {PatientContext.Value(c.CowInf, "CowRomCod")}",
["OCM_병동_병실_베드"] = c => c.CowInf.Count == 0 ? string.Empty
: $"{PatientContext.Value(c.CowInf, "CowWadCod")} / {PatientContext.Value(c.CowInf, "CowRomCod")}"
+ $" / {PatientContext.Value(c.CowInf, "CowBedCod")}",
// 내원유형 — 입원/응급실/외래(bzDataInterface.vb:2896-2918).
// 응급 여부는 내원행의 ComEmgYon 이다. 그 밖의 환자구분은 레거시도 "" 다.
["OCM_내원유형"] = c => Com(c, "ComPatTyp") switch
{
"I" => "입원",
"O" => Com(c, "ComEmgYon") == "Y" ? "응급실" : "외래",
_ => string.Empty,
},
};
private static string Pat(PatientContext c, string column) => PatientContext.Value(c.PatInf, column);
@@ -294,7 +311,8 @@ public sealed class PatientTagResolver : ITagValueResolver
/// </summary>
private TagValue? FromDoctor(string tag)
{
if (tag is not ("OCM_담당의사" or "OCM_담당의사_담당의사코드" or "OCM_담당의사_연락처"))
if (tag is not ("OCM_담당의사" or "OCM_담당의사_담당의사코드" or "OCM_담당의사_연락처"
or "OCM_담당의사_영문" or "OCM_담당의사면허번호" or "OCM_담당의사전문의번호"))
{
return null;
}
@@ -305,6 +323,11 @@ public sealed class PatientTagResolver : ITagValueResolver
{
"OCM_담당의사" => name,
"OCM_담당의사_담당의사코드" => name.Length > 0 ? $"{name}({Field("UidCod")})" : string.Empty,
// 아래 셋은 같은 담당의 행의 다른 컬럼일 뿐이다(bzDataInterface.vb:4094·4119·4144 —
// 조회 갈래가 OCM_담당의사와 문자 그대로 같고 마지막에 집는 컬럼만 다르다)
"OCM_담당의사_영문" => Field("UidEngNam"),
"OCM_담당의사면허번호" => Field("UidLicNum"),
"OCM_담당의사전문의번호" => Field("UidSpcLic"),
_ => Field("UidMblPhn"),
};
return value.Length > 0
@@ -343,6 +366,39 @@ public sealed class PatientTagResolver : ITagValueResolver
return hospitalName.Trim().Length > 0
? new TagValue(true, hospitalName.Trim())
: new TagValue(false, "요양기관명을 찾지 못했습니다(M_DepMst·M_HspMst 조인)");
// ── User 계열 — 로그인 사용자가 의사면 <b>사용자의</b> 과·이름, 아니면 환자의 것 ──
// (bzDataInterface.vb:5697-5826). 개원일 조건(HspStrDte>=20220701)은 세션에 없어
// 빈 값 결정이라 DepOfcNam 갈래를 타지 않는다 — OCM_진료과와 같은 결정이다.
case "OCM_User진료과":
case "OCM_User진료과_한글명칭":
{
if (UserSession.Current.DoctorYn == "Y")
{
var mine = UserSession.Current.DepNam;
return mine.Length > 0
? new TagValue(true, mine)
: new TagValue(false, "로그인 사용자의 부서명을 찾지 못했습니다");
}
var korNam = department.TryGetValue("DepKorNam", out var d) ? d.Trim() : string.Empty;
return korNam.Length > 0
? new TagValue(true, korNam)
: new TagValue(false, "이 내원의 진료과를 찾지 못했습니다");
}
case "OCM_User진료의사명":
{
if (UserSession.Current.DoctorYn == "Y")
{
return new TagValue(true, UserSession.Current.UidNam);
}
// 의사가 아니면 환자의 담당의 — 이미 든 담당의 행을 쓴다.
// 레거시는 접수일자로 유효기간을 다시 보지만(:5822) 우리 담당의 행과
// 사람이 갈리려면 같은 코드의 이력이 겹쳐야 한다 — 폴백까지 같아서 실질 동일.
var name = doctor.TryGetValue("UidNam", out var n) ? n.Trim() : string.Empty;
return name.Length > 0
? new TagValue(true, name)
: new TagValue(false, "이 내원의 담당의를 찾지 못했습니다");
}
default:
return null;
}
@@ -363,6 +419,8 @@ public sealed class PatientTagResolver : ITagValueResolver
"PAT_우편번호", "PAT_도로명주소", "PAT_지번주소", "PAT_영문도로명주소",
"OCM_담당의사", "OCM_담당의사_담당의사코드", "OCM_담당의사_연락처",
"OCM_진료과", "ETC_요양기관명칭_병원명",
"OCM_담당의사_영문", "OCM_담당의사면허번호", "OCM_담당의사전문의번호",
"OCM_User진료과", "OCM_User진료과_한글명칭", "OCM_User진료의사명",
};
/// <summary>