using SheetMe.Core.Catalog;
using SheetMe.Data.Stores;
namespace SheetMe.Designer.Services;
///
/// 보정을 마친 주민번호를 만드는 유일한 자리.
///
/// 왜 한 곳으로 좁히는가: 이 값은 이 앱이 다루는 것 중 가장 민감하다.
/// 만드는 경로가 여러 곳이면 어디서 새는지 추적할 수 없고, 보정 순서가 어긋난 사본이 생긴다.
///
/// 값을 저장하지 않는다. 필요할 때 만들어 에 넘기고 끝낸다.
/// 로그에도 남기지 않는다 — 어떤 진단 리포트에도 값이 아니라 길이만 적는다.
///
/// 복호화는 DB 가 한다(). 앱은 키를 갖지 않는다.
/// 자세한 것은 docs/RESNUM-DECRYPT.md.
///
public static class PatientIdentity
{
#region Methods
///
/// 이 환자의 주민번호 — 못 만들면 빈 문자열.
///
/// 암호화 설정을 못 읽으면 만들지 않는다. 설정을 "암호화 아님"으로 가정하면
/// PatResNum 을 평문처럼 써서 암호문을 종이에 찍는다. 빈칸이 훨씬 낫다.
///
public static string ResidentNumberOf(PatientContext context)
{
var connection = ConfigService.Current.ConnectionString;
if (connection.Length == 0)
{
return string.Empty;
}
var hospital = UserSession.Current.HspCod;
try
{
var store = new PersonalDataStore(connection);
var flags = store.Flags();
var raw = flags.ResidentNumberEncrypted
// 암호화 병원 — PatResNum 은 쓰지 않는다. PatResEnc 를 DB 가 풀어 준다
? store.Decrypt(PatientContext.Value(context.PatInf, "PatResEnc"), hospital)
: PatientContext.Value(context.PatInf, "PatResNum");
return ResidentNumber.Normalize(
raw,
PatientContext.Value(context.PatInf, "PatBthDay"),
PatientContext.Value(context.PatInf, "PatNatCod"),
hospital);
}
catch (Exception ex)
{
// 사유는 남기지만 값은 남기지 않는다 — 예외 메시지에 SQL 이 섞여도 값은 바인드였다
AppLog.Audit($"[주민번호] 만들지 못했습니다: {ex.GetType().Name}");
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
}