From db969fc6b5e7bacada33e550f3da675c3d65fc1e Mon Sep 17 00:00:00 2001 From: Msystech Date: Wed, 19 Aug 2026 14:47:46 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=B8=EC=87=84=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=EA=B0=80=20=ED=95=B4=EC=84=9D=EA=B8=B0=EB=A5=BC=20=EC=9E=83?= =?UTF-8?q?=EA=B3=A0=20=EC=9E=88=EC=97=88=EB=8B=A4=20=E2=80=94=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EA=B3=BC=20=EB=8B=A4=EB=A5=B8=20=EC=A2=85=EC=9D=B4?= =?UTF-8?q?=EA=B0=80=20=EB=82=98=EC=99=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자가 미리보기 화면과 실제 인쇄 PDF 를 나란히 놓고서야 드러난 결함. 미리보기에는 환자 값(등록번호·성명·생년월일)이 찍히는데 같은 창의 '인쇄' 버튼으로 뽑은 종이는 태그 이름 그대로였다. ## 원인 BuildFixedDocument 가 BuildPageVisual(page) 을 인자 없이 불렀다 — 기본값이 tags=null, fields=null 이라 인쇄 경로에서만 해석기가 사라졌다. 화면에서 확인하고 인쇄하면 다른 종이가 나온다. 미리보기의 존재 이유를 무너뜨리는 결함이다. ## 고친 것 - Print/BuildFixedDocument 가 해석기 한 벌을 받아 끝까지 넘긴다. - 미리보기 창의 인쇄는 그 창이 화면에 그리는 것과 같은 해석기를 넘긴다. - 미리보기를 안 거친 인쇄(Ctrl+P)도 세션 환자를 따른다 — PatientSession.ResolversFor(document) 가 한 벌을 만들어 준다 (주민번호·주소·담당의를 그 자리에서 한 번씩 읽는다). - printFilter 는 인쇄에서 고정 true 다. 레거시 인쇄가 PrintOutPut=False 를 빼므로 미리보기의 보기 토글과 무관하다. ## 게이트를 추가했다 이 판정이 없어서 지금까지 못 잡았다: 인쇄(대조군): 해석기가 없으면 값이 없다 인쇄: 해석기를 넘기면 환자 값이 인쇄 종이에 찍힌다 인쇄: 미리보기 종이와 인쇄 종이의 글자가 같다 (문자열 완전 일치) 대조군이 먼저다 — 없으면 "값이 어디서든 나온다"와 구분되지 않는다. ## 남긴 것 PDF MediaBox 높이가 856 DIP(642pt)가 아니라 735pt 로 나오는 문제는 별도다 — 용지·여백 처리 조사가 필요하고 이 수정과 무관하게 재현된다. ## 게이트 - dotnet test 338/338 - --edit-smoke 실패 0 (인쇄 판정 3건 신규) - --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변 Co-Authored-By: Claude Opus 5 --- src/SheetMe.Designer/Diagnostics/EditSmoke.cs | 32 +++++++++++++++++++ .../Services/PatientSession.cs | 19 +++++++++++ src/SheetMe.Designer/Services/PrintService.cs | 24 +++++++++++--- .../ViewModels/MainViewModel.cs | 5 ++- .../Views/PreviewWindow.xaml.cs | 5 ++- 5 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs index 24e419a..d1c18ec 100644 --- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs @@ -742,6 +742,38 @@ public static class EditSmoke && afterSessionClear.Contains("[PAT_차트번호", StringComparison.Ordinal), afterSessionClear); + // ── 미리보기와 인쇄가 같은 종이인가 ── + // 인쇄 경로(BuildFixedDocument)가 해석기 없이 BuildPageVisual 을 불러서 + // 화면에는 환자 값이 찍히는데 인쇄물은 태그 이름 그대로였다 — + // 실제 인쇄 PDF 를 화면과 나란히 놓고서야 발견했다. 이 판정이 없어서 못 잡은 것이다. + string PrintPaper(SheetMe.Core.Catalog.ITagValueResolver? resolver) + { + var fixedDoc = Services.PrintService.BuildFixedDocument( + patientVm, resolver, null); + var fixedPage = fixedDoc.Pages[0].Child; + fixedPage.Measure(new System.Windows.Size(720, 856)); + fixedPage.Arrange(new Rect(0, 0, 720, 856)); + fixedPage.UpdateLayout(); + return CollectText(fixedPage); + } + + var printResolver = new Services.PatientTagResolver( + Fake("C-9006", "인쇄환자"), new StubTagResolver()); + // 대조군 — 해석기 없이 뽑으면 값이 없어야 한다. 이 줄이 없으면 아래 판정은 + // "원래 값이 어디서든 나온다"와 구분되지 않는다. + var printBlind = PrintPaper(null); + Check("인쇄(대조군): 해석기가 없으면 값이 없다", + !printBlind.Contains("C-9006", StringComparison.Ordinal), printBlind); + var printPaper = PrintPaper(printResolver); + var previewPaperSame = PatientPaper(printResolver); + Check("인쇄: 해석기를 넘기면 환자 값이 인쇄 종이에 찍힌다", + printPaper.Contains("C-9006", StringComparison.Ordinal) + && printPaper.Contains("인쇄환자", StringComparison.Ordinal), + printPaper); + Check("인쇄: 미리보기 종이와 인쇄 종이의 글자가 같다", + printPaper == previewPaperSame, + $"미리보기 {previewPaperSame.Length}자 vs 인쇄 {printPaper.Length}자"); + // 창을 닫은 뒤에도 이벤트가 남아 있으면 회수되지 않고, 죽은 창이 계속 그린다. // 닫고 다시 세션을 건드려 예외 없이 지나가는지 본다(구독이 남아 있으면 // 죽은 창의 PagesHost 를 만지다 터진다). diff --git a/src/SheetMe.Designer/Services/PatientSession.cs b/src/SheetMe.Designer/Services/PatientSession.cs index 539ac0d..734c4a3 100644 --- a/src/SheetMe.Designer/Services/PatientSession.cs +++ b/src/SheetMe.Designer/Services/PatientSession.cs @@ -51,5 +51,24 @@ public static class PatientSession => Current is { } context ? new PatientQueryVariableSource(context, UserSession.Current.UidCod) : null; + + /// + /// 이 문서용 태그·배선 해석기 한 벌 — 지금 세션 환자 기준. + /// + /// 미리보기를 거치지 않는 인쇄(Ctrl+P)가 쓴다. 미리보기 창은 자기 해석기를 창 수명만큼 + /// 들고 있지만, 여기는 한 번 뽑는 종이라 그때그때 만든다. + /// 주민번호·주소·담당의를 이 자리에서 한 번씩 읽는다 — 태그마다 읽으면 왕복이 늘어난다. + /// + public static (SheetMe.Core.Catalog.ITagValueResolver Tags, MDataTableRunner Fields) + ResolversFor(SheetMe.Core.Models.FormDocument document) + { + var session = new SessionTagResolver(); + SheetMe.Core.Catalog.ITagValueResolver tags = Current is { } picked + ? new PatientTagResolver(picked, session, + PatientIdentity.ResidentNumberOf(picked), PatientIdentity.AddressOf(picked), + PatientIdentity.DoctorOf(picked)) + : session; + return (tags, new MDataTableRunner(document, Variables())); + } #endregion } diff --git a/src/SheetMe.Designer/Services/PrintService.cs b/src/SheetMe.Designer/Services/PrintService.cs index ce7afd4..612ef7e 100644 --- a/src/SheetMe.Designer/Services/PrintService.cs +++ b/src/SheetMe.Designer/Services/PrintService.cs @@ -16,7 +16,9 @@ public static class PrintService { #region Methods /// 인쇄 대화상자 → 전체 페이지 인쇄 - public static void Print(DesignerViewModel designer, string documentName) + public static void Print(DesignerViewModel designer, string documentName, + SheetMe.Core.Catalog.ITagValueResolver? tags = null, + SheetMe.Core.Catalog.IDataFieldResolver? fields = null) { var dialog = new System.Windows.Controls.PrintDialog(); if (dialog.ShowDialog() != true) @@ -24,12 +26,22 @@ public static class PrintService return; } - var document = BuildFixedDocument(designer); + var document = BuildFixedDocument(designer, tags, fields); dialog.PrintDocument(document.DocumentPaginator, $"SheetMe — {documentName}"); } - /// 페이지 VM 목록 → FixedDocument (미리보기/인쇄 공용) - public static FixedDocument BuildFixedDocument(DesignerViewModel designer) + /// + /// 페이지 VM 목록 → FixedDocument (미리보기/인쇄 공용). + /// + /// 해석기를 반드시 받아 넘긴다. 전에는 인자 없이 BuildPageVisual(page) 을 불러서 + /// 태그·데이터소스 해석기가 인쇄 경로에서만 사라졌다 — 미리보기에는 환자 값이 + /// 찍히는데 그 창의 '인쇄' 버튼으로 뽑은 종이는 태그 이름 그대로였다. + /// 화면에서 확인하고 인쇄하면 다른 종이가 나오는 것 — 미리보기의 존재 이유를 무너뜨리는 결함이었다. + /// 실제 인쇄 PDF 와 미리보기 화면을 나란히 놓고서야 발견했다. + /// + public static FixedDocument BuildFixedDocument(DesignerViewModel designer, + SheetMe.Core.Catalog.ITagValueResolver? tags = null, + SheetMe.Core.Catalog.IDataFieldResolver? fields = null) { var document = new FixedDocument(); foreach (var page in designer.Pages) @@ -40,7 +52,9 @@ public static class PrintService Height = page.HeightDip, Background = page.PaperBrush, }; - fixedPage.Children.Add(BuildPageVisual(page)); + // 인쇄는 항상 '인쇄될 것만' 이다(레거시 인쇄가 PrintOutPut=False 를 뺀다) — + // 미리보기의 보기 토글과 무관하게 printFilter 는 고정 true 다. + fixedPage.Children.Add(BuildPageVisual(page, true, tags, fields)); var pageContent = new PageContent(); ((System.Windows.Markup.IAddChild)pageContent).AddChild(fixedPage); diff --git a/src/SheetMe.Designer/ViewModels/MainViewModel.cs b/src/SheetMe.Designer/ViewModels/MainViewModel.cs index 744b31a..6077b3b 100644 --- a/src/SheetMe.Designer/ViewModels/MainViewModel.cs +++ b/src/SheetMe.Designer/ViewModels/MainViewModel.cs @@ -978,9 +978,12 @@ internal sealed class MainViewModel : ViewModelBase { return; } + // 미리보기를 안 거친 인쇄도 세션 환자를 따른다 — 해석기를 안 넘기면 + // 태그 이름이 그대로 인쇄된다(미리보기 인쇄에서 실제로 그랬다). + var (tags, fields) = Services.PatientSession.ResolversFor(CurrentDesigner.Document); Services.PrintService.Print(CurrentDesigner, CurrentDesigner.Document.Title.Length > 0 ? CurrentDesigner.Document.Title - : CurrentDesigner.Document.FormId); + : CurrentDesigner.Document.FormId, tags, fields); } catch (Exception ex) { diff --git a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs index 39f0c15..595a9d1 100644 --- a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs +++ b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs @@ -257,9 +257,12 @@ public partial class PreviewWindow : Window { try { + // 이 창이 종이에 그리는 것과 같은 해석기로 인쇄한다. + // 안 넘기면 화면에는 환자 값이 찍히는데 인쇄물은 태그 이름 그대로가 된다 — + // 실제 인쇄 PDF 와 화면을 나란히 놓고서야 발견한 결함이다. PrintService.Print(designer, designer.Document.Title.Length > 0 ? designer.Document.Title - : designer.Document.FormId); + : designer.Document.FormId, tags, fields); } catch (Exception ex) {