diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
index dd8b223..9f6b503 100644
--- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
+++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
@@ -1112,6 +1112,40 @@ public static class EditSmoke
$"ValueText='{choiceRow.ValueText}' IsMixed={choiceRow.IsMixed}");
}
+ // 15-a00000) 태그 행이 사실대로 말하는가.
+ {
+ // 카탈로그에 없는 이름은 표식이 붙어야 한다 — 그러나 막지는 않는다.
+ // 인라인으로 직접 치는 것이 주 경로인데 검증이 하나도 없어서,
+ // 한 글자 틀려도 조용히 커밋되고 미리보기에서 빈칸으로만 드러났다.
+ var tagRow2 = new ViewModels.Inspector.TagPickerRowViewModel("데이터 태그",
+ "데이터 태그 선택", SheetMe.Core.Catalog.LegacyTagCatalog.DataInterfaceTags,
+ new[] { "PAT_이름" });
+
+ tagRow2.Initialize("PAT_이름");
+ Check("태그: 카탈로그에 있는 이름은 표식이 없다", !tagRow2.IsUnknownTag);
+
+ tagRow2.ValueText = "PAT_이룸"; // 한 글자 오타
+ Check("태그: 카탈로그에 없는 이름에 표식이 붙는다", tagRow2.IsUnknownTag);
+ Check("태그: 그래도 값은 그대로 둔다(사이트 커스텀 태그)", tagRow2.ValueText == "PAT_이룸",
+ tagRow2.ValueText);
+
+ tagRow2.Initialize(string.Empty);
+ Check("태그: 빈 값은 표식 대상이 아니다", !tagRow2.IsUnknownTag);
+ tagRow2.Initialize(null, isMixed: true);
+ Check("태그: 값이 갈리면 표식을 내지 않는다", !tagRow2.IsUnknownTag);
+
+ // 로그인 사용자 태그 — 피커가 쓰던 5-인자판은 면허·전문의·직종·부서·부서전화를
+ // 빈 문자열로 채워, 종이에는 값이 찍히는데 피커에서만 "비어 있습니다"가 나왔다
+ var withFields = SheetMe.Core.Catalog.TagPreviewCatalog.FromUser(
+ "ETC_로그인_의사면허번호", "011825", "홍길동", "010", "02",
+ "면허12345", "전문의678", "의사", "내과", "1234");
+ var withoutFields = SheetMe.Core.Catalog.TagPreviewCatalog.FromUser(
+ "ETC_로그인_의사면허번호", "011825", "홍길동", "010", "02");
+ Check("태그: 면허번호는 10-인자판에서만 값이 나온다",
+ withFields == "면허12345" && withoutFields.Length == 0,
+ $"10인자='{withFields}' 5인자='{withoutFields}'");
+ }
+
// 15-a0000) 레이어 목록이 계층과 범위를 말하는가.
//
// 사용자가 콕 집어 "레이어 구분이 잘 안 된다"고 한 곳이다. 원인은 색이 아니라 산수였다 —
diff --git a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
index c5118d9..e221c5b 100644
--- a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
+++ b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
@@ -918,9 +918,10 @@ public sealed class InspectorViewModel : ViewModelBase
PropEditorKind.DataInterfaceTag => new TagPickerRowViewModel(def.Label,
"데이터 태그 선택 — 자동 채움 원천(bzDataInterface)", LegacyTagCatalog.DataInterfaceTags,
LegacyTagUsageCatalog.SuggestFor(def.Key, controlType)),
+ // 액션은 값이 아니라 동작이다 — 피커가 데이터 태그용 값·사유 칸을 그리면 안 된다
PropEditorKind.DataActionTag => new TagPickerRowViewModel(def.Label,
"액션 태그 선택 — 더블클릭/버튼 액션(EN_DataActionTyp)", LegacyTagCatalog.DataActionTags,
- LegacyTagUsageCatalog.SuggestFor(def.Key, controlType)),
+ LegacyTagUsageCatalog.SuggestFor(def.Key, controlType)) { IsActionTag = true },
PropEditorKind.SqlQuery => new QueryRowViewModel(def.Label),
PropEditorKind.Mask => new MaskRowViewModel(def.Label),
// 배선 대상 목록은 '지금 이 서식'의 데이터소스다 — 런타임도 같은 서식 안에서만 이름을 찾는다
diff --git a/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs b/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs
index 08d9b59..23c91e9 100644
--- a/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs
+++ b/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs
@@ -503,6 +503,29 @@ public sealed class TagPickerRowViewModel : PropertyRowViewModel
/// 피커 대화상자 제목
public string PickerTitle { get; }
+ ///
+ /// 액션 태그 행인가 — 액션은 값이 아니라 동작이라 피커의 값·사유 칸 뜻이 다르다.
+ /// 데이터 태그용 문구를 그대로 쓰면 149종 전부에서 그 칸이 거짓이 된다.
+ ///
+ public bool IsActionTag { get; init; }
+
+ ///
+ /// 카탈로그에 없는 이름인가 — 값은 건드리지 않고 표식만 낸다.
+ ///
+ /// 인라인 완성으로 직접 치는 것이 이 행의 주 경로인데 거기에는 검증이 하나도 없었다.
+ /// 'PAT_이룸' 처럼 한 글자 틀리면 아무 표시 없이 커밋되고, 그 사실은 미리보기에서
+ /// 빈칸으로만 드러난다 — 빈칸의 원인이 오타인지 이 환자에게 자료가 없어서인지 가릴 수 없다.
+ ///
+ /// 막지는 않는다. 사이트마다 카탈로그에 없는 커스텀 태그를 쓰기 때문이다(글꼴 행과 같은 규칙).
+ ///
+ public bool IsUnknownTag => !IsMixed && ValueText.Length > 0
+ && !string.Equals(ValueText, "None", StringComparison.Ordinal)
+ && !Choices.Contains(ValueText, StringComparer.Ordinal);
+
+ /// 경고 문구 — 툴팁으로 보인다
+ public string UnknownTagHint => $"'{ValueText}' 은(는) 카탈로그에 없는 이름입니다. "
+ + "사이트 전용 태그면 그대로 두고, 오타면 고치세요 — 값이 안 나오면 종이에서 빈칸이 됩니다.";
+
///
/// 한 줄에 하나꼴로 접히는 긴 한글 태그를 몇 개까지 깔지.
/// 인스펙터 폭이 좁아 칩이 세로로 쌓인다 — 8개를 넘기면 다른 속성이 화면 밖으로 밀린다.
@@ -676,13 +699,16 @@ public sealed class TagPickerRowViewModel : PropertyRowViewModel
if (e.PropertyName == nameof(ValueText))
{
OnPropertyChanged(nameof(ShowSuggestions));
+ // 값이 바뀌면 '카탈로그에 없음' 표식도 다시 판정한다
+ OnPropertyChanged(nameof(IsUnknownTag));
+ OnPropertyChanged(nameof(UnknownTagHint));
}
};
}
private void OnBrowse()
{
- var dialog = new Views.TagPickerDialogView(PickerTitle, Choices, ValueText, preferredAll)
+ var dialog = new Views.TagPickerDialogView(PickerTitle, Choices, ValueText, preferredAll, IsActionTag)
{
Owner = System.Windows.Application.Current.MainWindow,
};
diff --git a/src/SheetMe.Designer/Views/InspectorView.xaml b/src/SheetMe.Designer/Views/InspectorView.xaml
index c9133cf..49654ac 100644
--- a/src/SheetMe.Designer/Views/InspectorView.xaml
+++ b/src/SheetMe.Designer/Views/InspectorView.xaml
@@ -622,6 +622,16 @@
+
+
private readonly IReadOnlyList preferred;
+ /// 액션 태그 목록인가 — 값 칸의 뜻이 데이터 태그와 완전히 다르다
+ private readonly bool isActionKind;
+
private const string FilterAll = "*";
private const string FilterPreferred = "!";
private const string FilterUsed = "+";
@@ -81,11 +84,16 @@ public partial class TagPickerDialogView : Window
#endregion
#region Constructors
+ ///
+ /// 액션 태그 목록인가 — 액션은 값이 아니라 동작이라 값·사유 칸의 뜻이 완전히 다르다.
+ /// 데이터 태그용 문구를 그대로 쓰면 149종 전부에서 오른쪽 설명이 거짓이 된다.
+ ///
public TagPickerDialogView(string title, IReadOnlyList tags, string? currentValue,
- IReadOnlyList? preferredTags = null)
+ IReadOnlyList? preferredTags = null, bool isActionTag = false)
{
allTags = tags;
preferred = preferredTags ?? Array.Empty();
+ isActionKind = isActionTag;
previewStore = new SheetMe.Data.Stores.TagPreviewStore(
Services.ConfigService.Current.ConnectionString);
InitializeComponent();
@@ -295,13 +303,21 @@ public partial class TagPickerDialogView : Window
///
private void ShowTagValue(string tag)
{
- if (previewStore is null || !previewStore.CanUseDb)
- {
- InfoValueBlock.Visibility = Visibility.Collapsed;
- return;
- }
InfoValueBlock.Visibility = Visibility.Visible;
+ // 액션 태그는 값이 아니다. 더블클릭·버튼이 실행하는 동작이라
+ // "환자 정보가 있어야 값이 나옵니다" 같은 데이터 태그용 사유를 붙이면
+ // 149종 전부에서 이 칸이 통째로 거짓말을 한다.
+ if (isActionKind)
+ {
+ InfoValue.Text = "값이 아니라 동작입니다";
+ InfoValueNote.Text = "더블클릭·버튼에서 실행됩니다 — 종이에 찍히는 값이 아닙니다.";
+ return;
+ }
+
+ // 값을 만들 수 없는 이유는 DB 없이도 안다(정적 판정이다).
+ // 전에는 DB 가드가 이 위에 있어서, 접속이 없으면 사유까지 통째로 감춰졌다 —
+ // 정작 그 사유가 가장 필요한 상황(접속 안 된 단말에서 서식을 훑을 때)에 안 보였다.
if (!TagPreviewCatalog.CanResolve(tag))
{
InfoValue.Text = "값을 만들 수 없습니다";
@@ -309,12 +325,29 @@ public partial class TagPickerDialogView : Window
return;
}
+ // 여기서부터는 실제 값이 필요하다 — 그때만 DB 를 따진다.
+ // (진단 스크린샷은 DB 없이 이 창을 띄우므로 이 가드가 없으면 늘 "값을 읽지 못했습니다"가 찍힌다)
+ if (previewStore is null || !previewStore.CanUseDb)
+ {
+ InfoValue.Text = "값은 접속된 단말에서 확인됩니다";
+ InfoValueNote.Text = TagPreviewCatalog.SourceText(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)
+ // 본판(10-인자)으로 부른다. 5-인자 위임판은 면허·전문의·직종·부서·부서전화를
+ // 빈 문자열로 채운다 — 그래서 의사면허번호·전문의번호·직종·부서·부서전화 계열이
+ // 피커에서는 "이 계정에는 값이 비어 있습니다"로 보이는데 종이에는 실제 값이 찍혔다.
+ // 값이 나오는 사용자 태그 중 다섯 갈래가 피커에서만 거짓말을 하고 있었다.
+ // 이 값들은 로그인 세션이 이미 들고 있다(HisUser 15컬럼) — 조회를 더 하지 않는다.
: TagPreviewCatalog.FromUser(tag, context.UidCod, context.UidNam,
- context.MobilePhone, context.OfficePhone);
+ context.MobilePhone, context.OfficePhone,
+ UserSession.Current.LicenseNumber, UserSession.Current.SpecialistNumber,
+ UserSession.Current.JobName, UserSession.Current.DepNam,
+ UserSession.Current.DepPhone);
if (value.Length > 0)
{