태그 값 줄 — 값이 나오면 값을, 아니면 왜 안 나오는지를
앞 커밋의 판정표를 화면에 붙였다. 우측 정보 패널에 '이 태그의 값' 한 칸. '미리보기'라 부르지 않는다. 실제로 값이 나오는 것은 384종 중 16종뿐이라 그렇게 이름 붙이면 나머지 368종이 고장 난 것처럼 보인다. 여기서 보여 주는 것은 값이거나 사유이고, 사유 쪽이 대부분이며 실은 그쪽이 더 쓸모 있다 — ETC_요양기관명칭_병원명(실사용 3위)이 왜 여기서 값이 안 나오는지는 지금 어디서도 알 수 없다. TagPreviewStore 를 만들었다. 왕복 2회로 끝난다 — 서버 시각 1회, M_UIDMST 1행 1회. 세션에 이미 있는 것(사용자 코드·이름) 말고 없는 것은 연락처 두 칸뿐이라 그 줄만 더 읽는다. 재료는 창당 한 번만 읽는다: 태그를 고를 때마다 치면 목록을 화살표로 훑는 동안 초당 몇 번씩 왕복한다. 지킨 것: · 고정 SQL 만. 사용자 입력이 SQL 에 닿는 경로를 만들지 않는다 — 이 창은 임의 쿼리를 돌리는 곳이 아니다. · CommandTimeout 5초. 이 저장소에 CommandTimeout 이 0건이었는데 ODP.NET 기본은 무제한이다. · 실패는 삼킨다. 미리보기가 안 되는 것과 태그를 못 고르는 것은 다른 일이다. · DB 접속이 없으면 줄 자체를 감춘다 — 진단 스크린샷이 DB 없이 이 창을 띄우므로 이 가드가 없으면 거기서 늘 '값을 읽지 못했습니다'가 찍힌다(--dialog-shots 57파일 그대로). · 값이 비는 두 갈래를 가른다. '읽지 못했다'와 '이 계정에 값이 없다'는 다른 일이다. ServerClock 을 internal 에서 public 으로 열었다(다른 변경 없음). 게이트: 테스트 277/277, --edit-smoke 0실패, --modal-check 2/2, --dialog-shots 57파일, --db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
836a3e843a
commit
7e0e201acb
@@ -11,11 +11,11 @@ namespace SheetMe.Data.Stores;
|
|||||||
/// <c>ORDER BY SdgUpdDtm DESC, SdgKey DESC</c> 로 활성 버전을 고르므로,
|
/// <c>ORDER BY SdgUpdDtm DESC, SdgKey DESC</c> 로 활성 버전을 고르므로,
|
||||||
/// PC 시계가 뒤로 밀린 단말에서 저장하면 활성 버전 판정이 뒤집힐 수 있다.
|
/// PC 시계가 뒤로 밀린 단말에서 저장하면 활성 버전 판정이 뒤집힐 수 있다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class ServerClock
|
public static class ServerClock
|
||||||
{
|
{
|
||||||
#region Types
|
#region Types
|
||||||
/// <summary>서버 시각 1회 조회 결과 — 레거시가 쓰는 3가지 포맷을 함께 제공</summary>
|
/// <summary>서버 시각 1회 조회 결과 — 레거시가 쓰는 3가지 포맷을 함께 제공</summary>
|
||||||
internal readonly record struct Stamp(DateTime Value)
|
public readonly record struct Stamp(DateTime Value)
|
||||||
{
|
{
|
||||||
/// <summary>yyyyMMdd (8자리) — ShtAdpDte 류</summary>
|
/// <summary>yyyyMMdd (8자리) — ShtAdpDte 류</summary>
|
||||||
public string Date8 => Value.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
|
public string Date8 => Value.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
using Oracle.ManagedDataAccess.Client;
|
||||||
|
|
||||||
|
namespace SheetMe.Data.Stores;
|
||||||
|
|
||||||
|
/// <summary>태그 값을 만드는 데 필요한 재료 — 서버 시각과 로그인 사용자</summary>
|
||||||
|
public readonly record struct TagPreviewContext(
|
||||||
|
string Minute12, string UidCod, string UidNam, string MobilePhone, string OfficePhone)
|
||||||
|
{
|
||||||
|
public static readonly TagPreviewContext Empty =
|
||||||
|
new(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty);
|
||||||
|
|
||||||
|
public bool HasClock => Minute12.Length >= 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 태그 값 미리보기의 재료를 한 번에 읽는다.
|
||||||
|
///
|
||||||
|
/// <b>왜 따로 두는가.</b> 서식생성기에서 값이 나오는 태그는 서버 시각 계열과 로그인 사용자 계열뿐이다
|
||||||
|
/// (나머지는 환자·내원 문맥이 있어야 하고 여기에는 그 문맥이 없다).
|
||||||
|
/// 그래서 조회는 <b>왕복 2회로 끝난다</b> — 시각 1회, 사용자 1행 1회.
|
||||||
|
/// 태그를 고를 때마다 DB 를 치면 목록을 화살표로 훑는 동안 초당 몇 번씩 왕복하게 되므로
|
||||||
|
/// 호출부가 결과를 세션 동안 들고 있게 만든다.
|
||||||
|
///
|
||||||
|
/// 고정 SQL 만 쓴다. 사용자 입력이 SQL 에 닿는 경로를 만들지 않는다 —
|
||||||
|
/// 이 창은 임의 쿼리를 돌리는 곳이 아니다.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TagPreviewStore
|
||||||
|
{
|
||||||
|
#region Member Fields
|
||||||
|
/// <summary>미리보기가 편집을 막으면 안 된다 — 늦으면 값 없이 넘어간다</summary>
|
||||||
|
private const int TimeoutSeconds = 5;
|
||||||
|
|
||||||
|
private readonly string connectionString;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
public TagPreviewStore(string connectionString) => this.connectionString = connectionString;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
/// <summary>접속 정보가 없으면 미리보기 줄 자체를 감춘다</summary>
|
||||||
|
public bool CanUseDb => connectionString.Length > 0;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
/// <summary>
|
||||||
|
/// 재료를 한 번에 읽는다. 실패는 삼킨다 —
|
||||||
|
/// 미리보기가 안 되는 것과 태그를 못 고르는 것은 다른 일이다.
|
||||||
|
/// </summary>
|
||||||
|
public TagPreviewContext Read(string uidCod, string uidNam)
|
||||||
|
{
|
||||||
|
if (!CanUseDb)
|
||||||
|
{
|
||||||
|
return TagPreviewContext.Empty;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = new OracleConnection(connectionString);
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
var stamp = ServerClock.Read(connection, null);
|
||||||
|
var (mobile, office) = ReadPhones(connection, uidCod);
|
||||||
|
return new TagPreviewContext(stamp.Minute12, uidCod, uidNam, mobile, office);
|
||||||
|
}
|
||||||
|
catch (OracleException)
|
||||||
|
{
|
||||||
|
return TagPreviewContext.Empty;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return TagPreviewContext.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>연락처 두 칸 — 세션에 없는 것은 이것뿐이라 이 한 줄만 더 읽는다</summary>
|
||||||
|
private static (string Mobile, string Office) ReadPhones(OracleConnection connection, string uidCod)
|
||||||
|
{
|
||||||
|
if (uidCod.Length == 0)
|
||||||
|
{
|
||||||
|
return (string.Empty, string.Empty);
|
||||||
|
}
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT UIDMBLPHN, UIDOFFTEL FROM M_UIDMST WHERE UIDCOD = :uid";
|
||||||
|
command.CommandTimeout = TimeoutSeconds;
|
||||||
|
command.Parameters.Add(new OracleParameter("uid", uidCod));
|
||||||
|
using var reader = command.ExecuteReader();
|
||||||
|
if (!reader.Read())
|
||||||
|
{
|
||||||
|
return (string.Empty, string.Empty);
|
||||||
|
}
|
||||||
|
return (reader.IsDBNull(0) ? string.Empty : reader.GetString(0),
|
||||||
|
reader.IsDBNull(1) ? string.Empty : reader.GetString(1));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -207,6 +207,27 @@
|
|||||||
<TextBlock x:Name="InfoUsageNote" FontSize="10.5" TextWrapping="Wrap" Margin="0,3,0,0"
|
<TextBlock x:Name="InfoUsageNote" FontSize="10.5" TextWrapping="Wrap" Margin="0,3,0,0"
|
||||||
Foreground="{DynamicResource B.Muted}"/>
|
Foreground="{DynamicResource B.Muted}"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
'이 태그의 값'. '미리보기'라 부르지 않는다 —
|
||||||
|
실제로 값이 나오는 것은 384종 중 16종뿐이라, 그렇게 이름 붙이면
|
||||||
|
나머지가 고장 난 것처럼 보인다. 여기서는 값이 나오면 값을,
|
||||||
|
안 나오면 왜 안 나오는지를 보여 준다(그쪽이 대부분이고 더 쓸모 있다).
|
||||||
|
-->
|
||||||
|
<StackPanel x:Name="InfoValueBlock">
|
||||||
|
<TextBlock Text="이 태그의 값" Style="{StaticResource InfoLabel}"/>
|
||||||
|
<Border CornerRadius="6" Padding="9,6"
|
||||||
|
Background="{DynamicResource B.Chip}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock x:Name="InfoValue" FontFamily="Consolas" FontSize="12.5"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{DynamicResource B.Ink}"/>
|
||||||
|
<TextBlock x:Name="InfoValueNote" FontSize="10.5" TextWrapping="Wrap"
|
||||||
|
Margin="0,3,0,0"
|
||||||
|
Foreground="{DynamicResource B.Muted}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel x:Name="InfoVariantBlock" Visibility="Collapsed">
|
<StackPanel x:Name="InfoVariantBlock" Visibility="Collapsed">
|
||||||
<TextBlock Text="변형" Style="{StaticResource InfoLabel}"
|
<TextBlock Text="변형" Style="{StaticResource InfoLabel}"
|
||||||
ToolTip="같은 값을 다른 형식으로 내는 태그들"/>
|
ToolTip="같은 값을 다른 형식으로 내는 태그들"/>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using SheetMe.Core.Catalog;
|
using SheetMe.Core.Catalog;
|
||||||
|
using SheetMe.Designer.Services;
|
||||||
|
|
||||||
namespace SheetMe.Designer.Views;
|
namespace SheetMe.Designer.Views;
|
||||||
|
|
||||||
@@ -68,6 +69,10 @@ public partial class TagPickerDialogView : Window
|
|||||||
private const string FilterUsed = "+";
|
private const string FilterUsed = "+";
|
||||||
private string activeFilter = FilterAll;
|
private string activeFilter = FilterAll;
|
||||||
private bool suppressCategoryReload;
|
private bool suppressCategoryReload;
|
||||||
|
|
||||||
|
/// <summary>값 재료 — 창당 한 번만 읽는다(서버 시각 1회 + 사용자 1행)</summary>
|
||||||
|
private readonly SheetMe.Data.Stores.TagPreviewStore? previewStore;
|
||||||
|
private SheetMe.Data.Stores.TagPreviewContext? previewContext;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
@@ -81,6 +86,8 @@ public partial class TagPickerDialogView : Window
|
|||||||
{
|
{
|
||||||
allTags = tags;
|
allTags = tags;
|
||||||
preferred = preferredTags ?? Array.Empty<string>();
|
preferred = preferredTags ?? Array.Empty<string>();
|
||||||
|
previewStore = new SheetMe.Data.Stores.TagPreviewStore(
|
||||||
|
Services.ConfigService.Current.ConnectionString);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
TitleText.Text = title;
|
TitleText.Text = title;
|
||||||
|
|
||||||
@@ -274,12 +281,56 @@ public partial class TagPickerDialogView : Window
|
|||||||
? Visibility.Visible
|
? Visibility.Visible
|
||||||
: Visibility.Collapsed;
|
: Visibility.Collapsed;
|
||||||
|
|
||||||
|
ShowTagValue(tag);
|
||||||
|
|
||||||
// 변형 — 같은 값을 다른 형식으로 내는 태그(PAT_주민번호 / _Dash / _Blind)
|
// 변형 — 같은 값을 다른 형식으로 내는 태그(PAT_주민번호 / _Dash / _Blind)
|
||||||
var variants = TagSearch.VariantsOf(tag, allTags);
|
var variants = TagSearch.VariantsOf(tag, allTags);
|
||||||
InfoVariants.ItemsSource = variants;
|
InfoVariants.ItemsSource = variants;
|
||||||
InfoVariantBlock.Visibility = variants.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
InfoVariantBlock.Visibility = variants.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 값 또는 못 만드는 이유.
|
||||||
|
///
|
||||||
|
/// DB 접속이 없으면 줄 자체를 감춘다 — 진단 스크린샷이 DB 없이 이 창을 띄우므로
|
||||||
|
/// 이 가드가 없으면 거기서 늘 "값을 읽지 못했습니다"가 찍힌다.
|
||||||
|
/// 재료는 창당 한 번만 읽는다(서버 시각 1회 + 사용자 1행). 태그를 고를 때마다 치면
|
||||||
|
/// 목록을 화살표로 훑는 동안 초당 몇 번씩 왕복한다.
|
||||||
|
/// </summary>
|
||||||
|
private void ShowTagValue(string tag)
|
||||||
|
{
|
||||||
|
if (previewStore is null || !previewStore.CanUseDb)
|
||||||
|
{
|
||||||
|
InfoValueBlock.Visibility = Visibility.Collapsed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InfoValueBlock.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
if (!TagPreviewCatalog.CanResolve(tag))
|
||||||
|
{
|
||||||
|
InfoValue.Text = "값을 만들 수 없습니다";
|
||||||
|
InfoValueNote.Text = TagPreviewCatalog.ReasonText(tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewContext ??= previewStore.Read(UserSession.Current.UidCod, UserSession.Current.UidNam);
|
||||||
|
var context = previewContext.Value;
|
||||||
|
var value = TagPreviewCatalog.KindOf(tag) == TagPreviewKind.Clock
|
||||||
|
? TagPreviewCatalog.FromClock(tag, context.Minute12)
|
||||||
|
: TagPreviewCatalog.FromUser(tag, context.UidCod, context.UidNam,
|
||||||
|
context.MobilePhone, context.OfficePhone);
|
||||||
|
|
||||||
|
if (value.Length > 0)
|
||||||
|
{
|
||||||
|
InfoValue.Text = value;
|
||||||
|
InfoValueNote.Text = TagPreviewCatalog.SourceText(tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 값이 비는 두 갈래를 가른다 — 읽지 못한 것과 이 계정에 값이 없는 것은 다른 일이다
|
||||||
|
InfoValue.Text = context.HasClock ? "이 계정에는 값이 비어 있습니다" : "값을 읽지 못했습니다";
|
||||||
|
InfoValueNote.Text = TagPreviewCatalog.SourceText(tag);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnCopyTag(object sender, RoutedEventArgs e)
|
private void OnCopyTag(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if ((TagList.SelectedItem as TagListItem)?.Name is { Length: > 0 } tag)
|
if ((TagList.SelectedItem as TagListItem)?.Name is { Length: > 0 } tag)
|
||||||
|
|||||||
Reference in New Issue
Block a user