환자 선택 — 첫 걸음이 틀렸다는 것을 화면이 말하게 한다

## 가장 흔한 입력이 조용히 0건이었다

차트번호는 <b>완전일치</b>다. 병원 P_PatInf 는 고정 폭이라 실제 값이 0으로 채워져 있는데
사람은 "12345" 처럼 앞의 0 을 빼고 친다 — 그러면 0건이다.
저장소는 주석으로 "병원별 zero-pad 후 전달"을 요구하는데(PatientVisitStore.cs:8)
유일한 호출부가 그 계약을 안 지키고 있었다.

<b>자릿수를 설정으로 정하지 않았다.</b> 병원마다 다르고 틀리면 멀쩡한 검색을 망친다.
대신 0건일 때만 8·10자리로 한 번 더 찾는다(레거시가 쓰던 두 폭이다).
찾았으면 "자릿수를 채워 '0000012345' 로 찾았습니다"를 적고 그 번호를 칸에 되쓴다 —
다음부터 그 번호를 쓸 수 있게. 틀릴 여지가 없는 방식이다.

## 0건일 때 무엇으로 찾았는지 말하지 않았다

유형이 틀려서 0건인 경우가 가장 흔한데(습관대로 이름을 쳤는데 유형은 차트번호)
화면에 힌트가 하나도 없어 접속이나 권한을 의심하게 된다.
"없습니다(차트번호 완전일치로 찾았습니다)"로 유형과 일치 방식을 함께 적는다.

## '기준 시점'이 무슨 뜻인지 알 수 없었다

입원 내원은 <b>구간</b>이라 어느 시점의 진료과·병실·보험 자격을 쓸지 정해야 하는데,
목록에 접수일시만 있어서 그것이 구간이라는 사실 자체가 안 보였다.
이미 조회돼 있으면서 화면에 없던 <b>퇴원일시</b> 열을 붙이고,
선택지를 '마지막(퇴원 시점)'·'처음(입원 시점)'으로 바꿔 무엇을 고르는지 글자로 말한다.

열을 하나 더하면서 상태 열이 화면 밖으로 밀리고 콤보 글자가 잘렸다 —
사진으로 확인하고 일곱 열이 다 들어오도록 폭을 다시 잡았다.

## 판정

- 단위 시험 388 · edit-smoke 390건 전건 통과
- --dialog-shots 69장 전건 통과(다크·라이트 눈으로 확인 — 열·콤보 잘림 없음)
- --modal-check · --maxrect · --scale-budget 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

## 남은 것

검색 유형 자동 판별(입력 모양으로 주민번호·휴대전화·성명·차트번호를 가려내는 것)은
안 넣었다 — 콤보를 사람 몰래 바꾸는 동작이라 "왜 유형이 저절로 바뀌지"를 새로 만든다.
0건 안내가 유형을 밝히게 된 것으로 같은 혼란의 대부분은 없어진다.
자릿수 보정 뒤 재조회는 DB 가 붙은 단말에서 실제 차트번호로 확인이 필요하다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-21 08:48:13 +09:00
co-authored by Claude Fable 5
parent d83478619e
commit f6b456753c
2 changed files with 55 additions and 10 deletions
@@ -52,10 +52,10 @@
<TextBlock Text="기준 시점" VerticalAlignment="Center" Margin="0,0,6,0" <TextBlock Text="기준 시점" VerticalAlignment="Center" Margin="0,0,6,0"
Foreground="{DynamicResource B.Muted}" Foreground="{DynamicResource B.Muted}"
ToolTip="입원 내원은 구간이라 어느 시점의 진료과·병실·보험 자격을 쓸지 정해야 합니다. 외래는 접수일시로 같습니다."/> ToolTip="입원 내원은 구간이라 어느 시점의 진료과·병실·보험 자격을 쓸지 정해야 합니다. 외래는 접수일시로 같습니다."/>
<ComboBox x:Name="EdgeCombo" Width="92" SelectedIndex="0" <ComboBox x:Name="EdgeCombo" Width="150" SelectedIndex="0"
AutomationProperties.Name="기준 시점"> AutomationProperties.Name="기준 시점">
<ComboBoxItem Content="마지막"/> <ComboBoxItem Content="마지막(퇴원 시점)"/>
<ComboBoxItem Content="처음"/> <ComboBoxItem Content="처음(입원 시점)"/>
</ComboBox> </ComboBox>
<Button x:Name="SelectButton" Content="선택" Padding="20,4" Margin="10,0,0,0" <Button x:Name="SelectButton" Content="선택" Padding="20,4" Margin="10,0,0,0"
Click="OnSelect" IsDefault="True" IsEnabled="False" Click="OnSelect" IsDefault="True" IsEnabled="False"
@@ -132,12 +132,16 @@
AutomationProperties.Name="내원 목록"> AutomationProperties.Name="내원 목록">
<ListView.View> <ListView.View>
<GridView> <GridView>
<GridViewColumn Header="구분" Width="52" DisplayMemberBinding="{Binding Kind}"/> <GridViewColumn Header="구분" Width="46" DisplayMemberBinding="{Binding Kind}"/>
<GridViewColumn Header="접수일시" Width="118" DisplayMemberBinding="{Binding AcceptedAtText}"/> <GridViewColumn Header="접수일시" Width="106" DisplayMemberBinding="{Binding AcceptedAtText}"/>
<GridViewColumn Header="진료과" Width="100" DisplayMemberBinding="{Binding Department}"/> <!-- 퇴원일시 — 이미 조회돼 있는데 화면에 없었다.
<GridViewColumn Header="담당의" Width="80" DisplayMemberBinding="{Binding Doctor}"/> 입원 내원이 '구간'이라는 사실이 이 열 하나로 눈에 보인다.
<GridViewColumn Header="보험" Width="86" DisplayMemberBinding="{Binding Insurance}"/> 그래야 아래 기준 시점 선택이 무슨 뜻인지 알 수 있다. -->
<GridViewColumn Header="상태" Width="66" DisplayMemberBinding="{Binding State}"/> <GridViewColumn Header="퇴원일시" Width="106" DisplayMemberBinding="{Binding LeftAtText}"/>
<GridViewColumn Header="진료과" Width="86" DisplayMemberBinding="{Binding Department}"/>
<GridViewColumn Header="담당의" Width="68" DisplayMemberBinding="{Binding Doctor}"/>
<GridViewColumn Header="보험" Width="74" DisplayMemberBinding="{Binding Insurance}"/>
<GridViewColumn Header="상태" Width="58" DisplayMemberBinding="{Binding State}"/>
</GridView> </GridView>
</ListView.View> </ListView.View>
</ListView> </ListView>
@@ -86,6 +86,15 @@ public partial class PatientPickerDialogView : Window
} }
} }
/// <summary>검색 유형의 사람이 읽는 이름 — 0건 안내가 "무엇으로 찾았는지" 말할 때 쓴다</summary>
private static string KindLabel(PatientSearchKind kind) => kind switch
{
PatientSearchKind.Name => "성명 접두일치",
PatientSearchKind.ResidentNumber => "주민번호 접두일치",
PatientSearchKind.MobilePhone => "휴대전화 부분일치",
_ => "차트번호 완전일치",
};
private void OnSearch(object sender, RoutedEventArgs e) private void OnSearch(object sender, RoutedEventArgs e)
{ {
if (store is null) if (store is null)
@@ -111,10 +120,42 @@ public partial class PatientPickerDialogView : Window
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
SearchButton.IsEnabled = false; SearchButton.IsEnabled = false;
var found = store.Search(kind, TermBox.Text); var found = store.Search(kind, TermBox.Text);
var padded = string.Empty;
// 차트번호는 <b>완전일치</b>다. 병원마다 자릿수가 정해져 있어 실제 값은 0으로 채워져 있는데
// (P_PatInf 가 고정 폭이다) 사람은 "12345" 처럼 앞의 0 을 빼고 친다 — 그러면 0건이다.
// 저장소는 주석으로 "병원별 zero-pad 후 전달"을 요구하는데 이 호출부가 그걸 안 지켰다.
//
// <b>자릿수를 설정으로 정하지 않는다.</b> 병원마다 다르고 틀리면 멀쩡한 검색을 망친다.
// 대신 0건일 때만 한 번 더 찾는다 — 8·10자리는 레거시가 쓰던 두 폭이다.
// 찾았으면 무엇으로 찾았는지 칸에 되써서 사용자가 그 번호를 기억하게 한다.
if (found.Count == 0 && kind == PatientSearchKind.ChartNumber
&& TermBox.Text.Trim() is { Length: > 0 } typed && typed.All(char.IsDigit))
{
foreach (var width in new[] { 8, 10 })
{
if (typed.Length >= width)
{
continue;
}
var retry = store.Search(kind, typed.PadLeft(width, '0'));
if (retry.Count > 0)
{
found = retry;
padded = typed.PadLeft(width, '0');
TermBox.Text = padded;
break;
}
}
}
PatientList.ItemsSource = found; PatientList.ItemsSource = found;
PatientCountText.Text = found.Count switch PatientCountText.Text = found.Count switch
{ {
0 => "환자 — 없습니다", // 0건일 때 <b>무엇으로 찾았는지</b> 말한다 — 유형이 틀려서 0건인 경우가 가장 흔한데
// 전에는 그 힌트가 화면에 하나도 없어 접속이나 권한을 의심하게 됐다
0 => $"환자 — 없습니다({KindLabel(kind)}로 찾았습니다)",
_ when padded.Length > 0 => $"환자 {found.Count}명 — 자릿수를 채워 '{padded}' 로 찾았습니다",
PatientVisitStore.DefaultLimit => PatientVisitStore.DefaultLimit =>
$"환자 {found.Count}명 — 상한에 걸렸습니다. 검색어를 좁히세요", $"환자 {found.Count}명 — 상한에 걸렸습니다. 검색어를 좁히세요",
_ => $"환자 {found.Count}명", _ => $"환자 {found.Count}명",