미리보기에 태그 값이 흐를 관을 판다 — 값 아니면 사유

미리보기 레거시 호환 계획의 A1.

지금까지 미리보기는 태그를 <b>글자 그대로</b> 찍었다 — PAT_이름 이 종이에 그렇게 인쇄됐다.
값이 흐를 관이 아예 없었다.

<b>해석기를 인터페이스로 둔다.</b> 오늘 값이 나오는 것은 서버 시각·로그인 사용자 16종
(운영 사용 579건, 9.1%)뿐이고 나머지 368종은 환자·내원 문맥이 있어야 한다.
그 문맥이 생기면 <b>같은 자리로</b> 더 강한 해석기가 들어온다 — 관을 지금 파 두고 해석기만 갈아 끼운다.
A1 의 값어치는 9.1% 가 아니라 그 관이다.

<b>못 만든 값을 감추지 않는다.</b> 값이 나오면 값을, 안 나오면 사유를 대괄호로 감싸 그린다 —
[PAT_차트번호 — 환자·내원 정보가 있어야 값이 나오는 태그입니다].
빈칸으로 두면 서식이 잘못된 줄 알고, 태그 이름을 그대로 두면 값이 나온 줄 안다.
레거시도 같은 문제를 같은 이유로 다뤘다 — 실패한 컨트롤을 노란색으로 칠했다
(ucLoadSheetBase.vb:2300-2308). 그 장치가 있다는 것 자체가 현장 실패가 흔하다는 방증이다.

주입은 컨테이너 투영과 같은 방식이다 — 모델을 복제해 Text 만 갈아 끼우고 같은 타입의 VM 을 만든다.
<b>캔버스가 쓰는 VM 과 모델은 건드리지 않는다.</b>

DB 는 창당 한 번만 읽는다(재료 왕복 2회). 그리기마다 새로 만들면 다시 그릴 때마다 DB 를 친다 —
레거시가 태그마다 쿼리를 돌려 태그 40개 서식에 왕복 40회를 하는 것과 같은 실수다.

<b>단정은 종이에 찍힌 글자를 본다.</b> VM 속성만 보면 템플릿이 그 값을 쓰는지 알 수 없다 —
배선과 결과는 다른 문제이고, 이 저장소에서 그 둘이 여러 번 갈렸다.
검사용 해석기를 따로 둬 접속 없이도 같은 답이 나오게 했다.

게이트: 테스트 298/298, --edit-smoke 0실패(신규 3), --dialog-shots FAIL 0,
--modal-check 0실패, --cleartype 11/11, --scale-budget 5/5, 빌드 경고 0,
--db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-18 10:04:49 +09:00
co-authored by Claude Opus 5
parent a67b6ee4b8
commit 26e93cae94
5 changed files with 216 additions and 6 deletions
@@ -32,6 +32,34 @@ public static class EditSmoke
return total;
}
/// <summary>
/// 종이에 실제로 그려진 글자를 모은다 — 값이 <b>찍혔는지</b>는 시각 트리에서 확인해야 한다.
/// VM 속성만 보면 템플릿이 그 값을 쓰는지 알 수 없다(배선과 결과는 다른 문제다).
/// </summary>
private static string CollectText(System.Windows.DependencyObject node)
{
var text = node is System.Windows.Controls.TextBlock block ? block.Text + "\n" : string.Empty;
var count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(node);
for (var i = 0; i < count; i++)
{
text += CollectText(System.Windows.Media.VisualTreeHelper.GetChild(node, i));
}
return text;
}
/// <summary>
/// 검사용 해석기 — 실제 DB 없이 "값이 나오는 태그"와 "안 나오는 태그" 두 갈래를 만든다.
/// 진단은 접속 없이도 같은 답을 내야 하므로 여기서 세션 해석기를 쓰지 않는다.
/// </summary>
private sealed class StubTagResolver : SheetMe.Core.Catalog.ITagValueResolver
{
public SheetMe.Core.Catalog.TagValue Resolve(string tag)
=> SheetMe.Core.Catalog.TagPreviewCatalog.CanResolve(tag)
? new SheetMe.Core.Catalog.TagValue(true, "2026-08-18")
: new SheetMe.Core.Catalog.TagValue(false,
SheetMe.Core.Catalog.TagPreviewCatalog.ReasonText(tag));
}
public static int Run(string reportPath)
{
var lines = new List<string>();
@@ -378,6 +406,53 @@ public static class EditSmoke
Check("미리보기: 컨트롤을 추가하면 창이 따라온다", liveAfterAdd > livePages,
$"요소 {livePages} → {liveAfterAdd} (안 늘면 생성자에서 한 번만 그린 것)");
live.Close();
// 태그가 종이에서 값이 되는가.
// 지금까지 미리보기는 태그를 글자 그대로 찍었다 — PAT_이름 이 종이에 그렇게 인쇄됐다.
// 해석기를 붙이면 값이 나오는 것은 값으로, 못 나오는 것은 <b>사유</b>로 바뀌어야 한다
// (빈칸이면 서식이 잘못된 줄 알고, 태그 이름 그대로면 값이 나온 줄 안다).
var tagged = new FormDocument();
var tagPage = new FormPage { Width = 720, Height = 856 };
tagged.Pages.Add(tagPage);
foreach (var (id, tag) in new[] { ("시각", "ETC_현재일자"), ("환자", "PAT_차트번호") })
{
var field = new ControlElement
{
Type = "TextBox", Id = id,
Bounds = new LayoutRect { X = 40, Y = id == "시각" ? 40 : 80, W = 200, H = 20 },
};
field.Props.SetText("Text", tag);
field.Props.SetText("DataInterfaceTag", tag);
tagPage.Controls.Add(field);
}
var taggedVm = new DesignerViewModel(tagged);
string PaperText(SheetMe.Core.Catalog.ITagValueResolver? resolver)
{
var visual = (System.Windows.FrameworkElement)
Services.PrintService.BuildPageVisual(taggedVm.Pages[0], true, resolver);
visual.Measure(new System.Windows.Size(720, 856));
visual.Arrange(new Rect(0, 0, 720, 856));
visual.UpdateLayout();
return CollectText(visual);
}
// 해석기가 없으면 지금까지와 같다 — 태그 이름 그대로
var noResolver = PaperText(null);
Check("태그 값: 해석기가 없으면 태그 이름 그대로",
noResolver.Contains("PAT_차트번호", StringComparison.Ordinal)
&& !noResolver.Contains("[", StringComparison.Ordinal),
noResolver);
// 값이 나오는 태그는 값으로, 안 나오는 태그는 사유로
var withResolver = PaperText(new StubTagResolver());
Check("태그 값: 값이 나오는 태그는 값이 찍힌다",
withResolver.Contains("2026-08-18", StringComparison.Ordinal), withResolver);
Check("태그 값: 못 만드는 태그는 사유가 찍힌다",
withResolver.Contains("PAT_차트번호", StringComparison.Ordinal)
&& withResolver.Contains("환자", StringComparison.Ordinal)
&& withResolver.Contains("[", StringComparison.Ordinal),
withResolver);
}
// 2) 클릭 선택 — 컨트롤 중심(핸들 반경 밖)