diff --git a/src/SheetMe.Data/Stores/PatientContextStore.cs b/src/SheetMe.Data/Stores/PatientContextStore.cs index 58fcb89..3f69d62 100644 --- a/src/SheetMe.Data/Stores/PatientContextStore.cs +++ b/src/SheetMe.Data/Stores/PatientContextStore.cs @@ -124,5 +124,47 @@ public sealed class PatientContextStore } private static string Day(string stamp) => stamp.Length >= 8 ? stamp[..8] : stamp; + + /// + /// 건물관리번호로 주소 — 레거시 GetZipInfoByBuildingNumber(clsCommonLib.vb:16600). + /// + /// 결합식을 그대로 옮긴다. DECODE 로 빈 조각을 건너뛰고 구분자를 붙이는 규칙이 + /// 도로명·지번·영문이 각각 다르다 — 손으로 다시 짜면 주소가 미묘하게 달라진다. + /// + /// 없으면 빈 사전. 레거시도 못 찾으면 Nothing 을 돌려주고 호출부가 상세주소만 쓴다. + /// + public IReadOnlyDictionary Address(string? buildingNumber) + { + var key = (buildingNumber ?? string.Empty).Trim(); + if (key.Length == 0) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + using var connection = new OracleConnection(connectionString); + connection.Open(); + return Row(connection, + "SELECT 구역번호, 우편번호" + + ", TRIM(시도) || ' ' || DECODE(TRIM(시군구), NULL, '', TRIM(시군구) || ' ')" + + " || DECODE(TRIM(읍면), NULL, '', TRIM(읍면) || ' ')" + + " || DECODE(TRIM(도로명), NULL, '', TRIM(도로명) || ' ')" + + " || DECODE(지하여부, 1, '지하 ', '')" + + " || DECODE(TRIM(건물번호본번), NULL, '', TRIM(건물번호본번))" + + " || DECODE(TRIM(건물번호부번), NULL, ' ', 0, ' ', '-' || TRIM(건물번호부번) || ' ')" + + " || DECODE(TRIM(시군구용건물명), NULL, '', TRIM(시군구용건물명) || '') AS 도로명주소" + + ", TRIM(시도) || ' ' || DECODE(TRIM(시군구), NULL, '', TRIM(시군구) || ' ')" + + " || DECODE(TRIM(읍면), NULL, '', TRIM(읍면) || ' ')" + + " || DECODE(TRIM(법정동명), NULL, '', TRIM(법정동명) || ' ')" + + " || DECODE(TRIM(리명), NULL, '', TRIM(리명) || ' ')" + + " || DECODE(산여부, 1, ' 산 ', '')" + + " || DECODE(TRIM(지번본번), NULL, '', TRIM(지번본번))" + + " || DECODE(TRIM(지번부번), NULL, ' ', 0, ' ', '-' || TRIM(지번부번) || ' ')" + + " || DECODE(TRIM(시군구용건물명), NULL, '', TRIM(시군구용건물명) || '') AS 지번주소" + + ", DECODE(TRIM(건물번호본번), NULL, '', TRIM(건물번호본번) || ' ')" + + " || DECODE(TRIM(도로명영문), NULL, '', TRIM(도로명영문) || ', ')" + + " || DECODE(TRIM(읍면영문), NULL, '', TRIM(읍면영문) || ', ')" + + " || DECODE(TRIM(시군구영문), NULL, '', TRIM(시군구영문) || ', ')" + + " || TRIM(시도영문) AS 영문도로명주소" + + " FROM M_ZipMst WHERE 건물관리번호 = :b", ("b", key)); + } #endregion } diff --git a/src/SheetMe.Designer/Services/PatientIdentity.cs b/src/SheetMe.Designer/Services/PatientIdentity.cs index 4cbd6af..a086ef0 100644 --- a/src/SheetMe.Designer/Services/PatientIdentity.cs +++ b/src/SheetMe.Designer/Services/PatientIdentity.cs @@ -54,5 +54,31 @@ public static class PatientIdentity return string.Empty; } } + + /// + /// 이 환자의 주소 조각들(구역번호·도로명·지번·영문) — 못 읽으면 빈 사전. + /// + /// 주민번호와 같은 이유로 환자를 붙일 때 한 번만 읽는다 — + /// 주소 태그가 넷이라 태그마다 읽으면 왕복이 넷이 된다. + /// 실패해도 던지지 않는다: 호출부가 상세주소만으로 값을 만들 수 있고, 레거시도 그렇게 한다. + /// + public static IReadOnlyDictionary AddressOf(PatientContext context) + { + var connection = ConfigService.Current.ConnectionString; + var empty = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (connection.Length == 0) + { + return empty; + } + try + { + return new PatientContextStore(connection) + .Address(PatientContext.Value(context.PatInf, "PatZipBdg")); + } + catch + { + return empty; + } + } #endregion } diff --git a/src/SheetMe.Designer/Services/PatientTagResolver.cs b/src/SheetMe.Designer/Services/PatientTagResolver.cs index 2245231..caa9c40 100644 --- a/src/SheetMe.Designer/Services/PatientTagResolver.cs +++ b/src/SheetMe.Designer/Services/PatientTagResolver.cs @@ -128,12 +128,16 @@ public sealed class PatientTagResolver : ITagValueResolver #endregion #region Constructors + /// M_ZipMst 에서 만든 주소 조각들 — 없으면 상세주소만 쓴다 + private readonly IReadOnlyDictionary address; + public PatientTagResolver(PatientContext context, ITagValueResolver next, - string residentNumber = "") + string residentNumber = "", IReadOnlyDictionary? address = null) { this.context = context; this.next = next; this.residentNumber = residentNumber; + this.address = address ?? new Dictionary(StringComparer.OrdinalIgnoreCase); } #endregion @@ -145,6 +149,10 @@ public sealed class PatientTagResolver : ITagValueResolver { return derived; } + if (FromAddress(tag) is { } located) + { + return located; + } if (Computed.TryGetValue(tag, out var compute)) { var made = compute(context); @@ -218,6 +226,40 @@ public sealed class PatientTagResolver : ITagValueResolver } } + /// + /// 주소 네 종 — 해당 태그가 아니면 null. + /// + /// 결합 규칙이 셋 다 다르다(bzDataInterface.vb 우편번호·도로명·지번·영문도로명). + /// 도로명은 주소 + " " + 상세, 지번은 주소 + 상세(공백 없음), + /// 영문은 상세 + " " + 주소(순서가 뒤바뀐다). + /// 눈으로 보면 오타 같지만 레거시가 그렇게 찍는다 — 맞춰야 같은 종이가 된다. + /// + /// 우편번호 태그가 돌려주는 것은 우편번호 컬럼이 아니라 구역번호다. + /// 조회는 둘 다 가져오지만 레거시가 쓰는 것은 구역번호다. + /// + /// 주소를 못 찾으면 상세주소만 돌려준다 — 레거시와 같다. 빈 값이 아니다. + /// + private TagValue? FromAddress(string tag) + { + if (tag is not ("PAT_우편번호" or "PAT_도로명주소" or "PAT_지번주소" or "PAT_영문도로명주소")) + { + return null; + } + var detail = Pat(context, "PatAdrDtl"); + var found = (string column) => address.TryGetValue(column, out var v) ? v.Trim() : string.Empty; + + var value = tag switch + { + "PAT_우편번호" => found("구역번호"), + "PAT_도로명주소" => found("도로명주소").Length > 0 ? found("도로명주소") + " " + detail : detail, + "PAT_지번주소" => found("지번주소").Length > 0 ? found("지번주소") + detail : detail, + _ => found("영문도로명주소").Length > 0 ? detail + " " + found("영문도로명주소") : detail, + }; + return value.Trim().Length > 0 + ? new TagValue(true, value) + : new TagValue(false, "이 환자에게는 주소 정보가 없습니다"); + } + /// 주민번호에서 나온 값 — 비면 비었는지 말한다 private TagValue Wrap(string value) => value.Length > 0 ? new TagValue(true, value) @@ -230,6 +272,7 @@ public sealed class PatientTagResolver : ITagValueResolver public static readonly string[] DerivedTags = { "PAT_주민번호", "PAT_주민번호_Dash", "PAT_성별", "PAT_한글성별", + "PAT_우편번호", "PAT_도로명주소", "PAT_지번주소", "PAT_영문도로명주소", }; /// diff --git a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs index 5f135dc..cd4e203 100644 --- a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs +++ b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs @@ -221,7 +221,8 @@ public partial class PreviewWindow : Window // 가장 민감한 값을 여러 번 꺼내게 된다. tags = picked is null ? session - : new PatientTagResolver(picked, session, PatientIdentity.ResidentNumberOf(picked)); + : new PatientTagResolver(picked, session, + PatientIdentity.ResidentNumberOf(picked), PatientIdentity.AddressOf(picked)); // 러너를 새로 만든다. 같은 러너를 재사용하면 옛 환자의 조회 결과가 캐시에 남아 // 환자를 바꿨는데 표는 그대로가 된다 — 태그만 바뀌고 표는 안 바뀌면 아무도 눈치채지 못한다. fields = new MDataTableRunner(designer.Document,