commit 5ea7131636bc32b8b8da34b61f1e049c846d634c
Author: Jens Kristoffersson <jens.kristoffersson.se@gmail.com>
AuthorDate: Wed Sep 2 09:20:55 2026 +0200
Commit: Jens Kristoffersson <jens.kristoffersson.se@gmail.com>
CommitDate: Wed Sep 2 09:20:55 2026 +0200
Tom targetPrinter = Windows standardskrivare
SumatraPdfPrinter använder -print-to-default när targetPrinter är tom
(inga dialoger), annars -print-to "<namn>". DefaultPrinter läser
standardskrivarens namn ur registret för audit-loggen. appsettings
default ändrad till "" + kommentarer. IPrinter.Print tar string?.
Nya tester: blank printer-väg, utskriftsfel, DefaultPrinter. 19/19.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
config/appsettings.json | 11 ++++++++-
src/Remiss.Core/Abstractions/IPrinter.cs | 3 ++-
src/Remiss.Core/AppConfig.cs | 2 ++
src/Remiss.Core/Printing/DefaultPrinter.cs | 32 ++++++++++++++++++++++++++
src/Remiss.Core/Printing/SumatraPdfPrinter.cs | 24 ++++++++++++-------
src/Remiss.Core/RemissWorkflow.cs | 7 +++++-
tests/Remiss.Core.Tests/DefaultPrinterTests.cs | 15 ++++++++++++
tests/Remiss.Core.Tests/WorkflowTests.cs | 23 ++++++++++++++++--
8 files changed, 104 insertions(+), 13 deletions(-)
diff --git a/config/appsettings.json b/config/appsettings.json
index 6c3a5a3..838f58b 100644
--- a/config/appsettings.json
+++ b/config/appsettings.json
@@ -1,12 +1,21 @@
{
- "targetPrinter": "Microsoft Print to PDF",
+ // Namngiven skrivare, eller "" för Windows standardskrivare (inga dialoger).
+ "targetPrinter": "",
+
"sumatraPdfPath": "tools/SumatraPDF.exe",
"mottagningarPath": "mottagningar.json",
+
+ // Tomma = under %LOCALAPPDATA%\Remiss
"secureTempRoot": "",
"auditLogPath": "",
"errorLogPath": "",
+
"printRetries": 3,
"printRetryDelayMs": 2000,
+
+ // false i Fas 1 (behåll test-PDF:en), true i Fas 2
"deleteSourceAfterPrint": false,
+
+ // ENDAST TEST: kopiera mergad PDF hit innan utskrift/städning. Tom i drift.
"debugOutputDir": ""
}
diff --git a/src/Remiss.Core/Abstractions/IPrinter.cs b/src/Remiss.Core/Abstractions/IPrinter.cs
index 4158365..9dd1a74 100644
--- a/src/Remiss.Core/Abstractions/IPrinter.cs
+++ b/src/Remiss.Core/Abstractions/IPrinter.cs
@@ -6,5 +6,6 @@ namespace Remiss.Core.Abstractions;
/// </summary>
public interface IPrinter
{
- void Print(string pdfPath, string printerName);
+ /// <param name="printerName">Namngiven skrivare, eller tom/null för systemets standardskrivare.</param>
+ void Print(string pdfPath, string? printerName);
}
diff --git a/src/Remiss.Core/AppConfig.cs b/src/Remiss.Core/AppConfig.cs
index 1bf5776..19795b2 100644
--- a/src/Remiss.Core/AppConfig.cs
+++ b/src/Remiss.Core/AppConfig.cs
@@ -9,7 +9,9 @@ namespace Remiss.Core;
/// </summary>
public sealed class AppConfig
{
+ /// <summary>Namngiven målskrivare. Tom = Windows standardskrivare (utan dialoger).</summary>
public string TargetPrinter { get; set; } = "";
+
public string SumatraPdfPath { get; set; } = "tools/SumatraPDF.exe";
public string MottagningarPath { get; set; } = "mottagningar.json";
public string SecureTempRoot { get; set; } = "";
diff --git a/src/Remiss.Core/Printing/DefaultPrinter.cs b/src/Remiss.Core/Printing/DefaultPrinter.cs
new file mode 100644
index 0000000..a638f57
--- /dev/null
+++ b/src/Remiss.Core/Printing/DefaultPrinter.cs
@@ -0,0 +1,32 @@
+using Microsoft.Win32;
+
+namespace Remiss.Core.Printing;
+
+/// <summary>
+/// Läser namnet på Windows standardskrivare. Används bara för audit-loggen –
+/// själva utskriften låter SumatraPDF välja default via <c>-print-to-default</c>.
+/// </summary>
+public static class DefaultPrinter
+{
+ public static string? TryGetName()
+ {
+ try
+ {
+ using var key = Registry.CurrentUser.OpenSubKey(
+ @"Software\Microsoft\Windows NT\CurrentVersion\Windows");
+
+ if (key?.GetValue("Device") is string device && device.Length > 0)
+ {
+ // Formatet är "Skrivarnamn,winspool,Ne00:"
+ var comma = device.IndexOf(',');
+ return comma > 0 ? device[..comma] : device;
+ }
+ }
+ catch
+ {
+ // ignore – audit får då "(systemets standardskrivare)"
+ }
+
+ return null;
+ }
+}
diff --git a/src/Remiss.Core/Printing/SumatraPdfPrinter.cs b/src/Remiss.Core/Printing/SumatraPdfPrinter.cs
index 1bc4ff0..7aea623 100644
--- a/src/Remiss.Core/Printing/SumatraPdfPrinter.cs
+++ b/src/Remiss.Core/Printing/SumatraPdfPrinter.cs
@@ -20,16 +20,15 @@ public sealed class SumatraPdfPrinter : IPrinter
_retryDelayMs = Math.Max(0, retryDelayMs);
}
- public void Print(string pdfPath, string printerName)
+ /// <param name="printerName">
+ /// Namngiven skrivare, eller tom/null för Windows standardskrivare.
+ /// </param>
+ public void Print(string pdfPath, string? printerName)
{
if (!File.Exists(_exePath))
throw new FileNotFoundException(
$"SumatraPDF hittades inte: {_exePath}. Lägg binären i tools/ (se tools/README.md).", _exePath);
- if (string.IsNullOrWhiteSpace(printerName))
- throw new InvalidOperationException(
- "Ingen målskrivare konfigurerad (targetPrinter i appsettings.json).");
-
Exception? last = null;
for (var attempt = 1; attempt <= _retries; attempt++)
{
@@ -49,7 +48,7 @@ public sealed class SumatraPdfPrinter : IPrinter
throw new InvalidOperationException($"Utskrift misslyckades efter {_retries} försök.", last);
}
- private void RunOnce(string pdfPath, string printerName)
+ private void RunOnce(string pdfPath, string? printerName)
{
var psi = new ProcessStartInfo
{
@@ -59,8 +58,17 @@ public sealed class SumatraPdfPrinter : IPrinter
RedirectStandardOutput = true,
RedirectStandardError = true,
};
- psi.ArgumentList.Add("-print-to");
- psi.ArgumentList.Add(printerName);
+
+ if (string.IsNullOrWhiteSpace(printerName))
+ {
+ psi.ArgumentList.Add("-print-to-default");
+ }
+ else
+ {
+ psi.ArgumentList.Add("-print-to");
+ psi.ArgumentList.Add(printerName);
+ }
+
psi.ArgumentList.Add("-silent");
psi.ArgumentList.Add(pdfPath);
diff --git a/src/Remiss.Core/RemissWorkflow.cs b/src/Remiss.Core/RemissWorkflow.cs
index 240426e..3dae6de 100644
--- a/src/Remiss.Core/RemissWorkflow.cs
+++ b/src/Remiss.Core/RemissWorkflow.cs
@@ -2,6 +2,7 @@ using Remiss.Core.Abstractions;
using Remiss.Core.Audit;
using Remiss.Core.Models;
using Remiss.Core.Pdf;
+using Remiss.Core.Printing;
using Remiss.Core.Security;
namespace Remiss.Core;
@@ -41,13 +42,17 @@ public sealed class RemissWorkflow
DumpForDebug(mergedPath);
stage = "print";
+ var printerName = string.IsNullOrWhiteSpace(_config.TargetPrinter)
+ ? DefaultPrinter.TryGetName() ?? "(systemets standardskrivare)"
+ : _config.TargetPrinter;
_printer.Print(mergedPath, _config.TargetPrinter);
stage = "source-cleanup";
if (_config.DeleteSourceAfterPrint)
SecureFile.Shred(sourcePdfPath);
- _audit.Write("remiss.sent", mottagning.Namn, mottagning.HsaId, success: true, stage: "done");
+ _audit.Write("remiss.sent", mottagning.Namn, mottagning.HsaId,
+ success: true, stage: "done", detail: $"printer={printerName}");
}
catch (Exception ex)
{
diff --git a/tests/Remiss.Core.Tests/DefaultPrinterTests.cs b/tests/Remiss.Core.Tests/DefaultPrinterTests.cs
new file mode 100644
index 0000000..a1a5a90
--- /dev/null
+++ b/tests/Remiss.Core.Tests/DefaultPrinterTests.cs
@@ -0,0 +1,15 @@
+using Remiss.Core.Printing;
+using Xunit;
+
+namespace Remiss.Core.Tests;
+
+public sealed class DefaultPrinterTests
+{
+ [Fact]
+ public void TryGetName_neverThrows_andReturnsNullOrANonEmptyName()
+ {
+ var name = DefaultPrinter.TryGetName();
+ Assert.True(name is null || name.Length > 0);
+ Assert.DoesNotContain(",", name ?? ""); // registervärdets ",winspool,..." ska vara bortkapat
+ }
+}
diff --git a/tests/Remiss.Core.Tests/WorkflowTests.cs b/tests/Remiss.Core.Tests/WorkflowTests.cs
index 34b5cf8..9ec7ee7 100644
--- a/tests/Remiss.Core.Tests/WorkflowTests.cs
+++ b/tests/Remiss.Core.Tests/WorkflowTests.cs
@@ -23,7 +23,7 @@ public sealed class WorkflowTests : IDisposable
{
public string? PrintedFile { get; private set; }
public string? Printer { get; private set; }
- public void Print(string pdfPath, string printerName)
+ public void Print(string pdfPath, string? printerName)
{
Assert.True(File.Exists(pdfPath)); // mergad fil finns när utskrift anropas
PrintedFile = pdfPath;
@@ -58,6 +58,25 @@ public sealed class WorkflowTests : IDisposable
Assert.Contains("remiss.sent", auditText);
}
+ [Fact]
+ public void Run_withBlankTargetPrinter_passesBlankThroughAndAuditsAPrinter()
+ {
+ var source = Path.Combine(_dir, "remiss.pdf");
+ CoverPageGenerator.Generate(source, new CoverInfo("Body", "hsa-body", DateTime.Now));
+
+ var cfg = new AppConfig { TargetPrinter = "" };
+ var audit = new AuditLogger(Path.Combine(_dir, "audit.jsonl"), Path.Combine(_dir, "error.log"));
+ var printer = new FakePrinter();
+
+ using (var temp = new SecureTempStore(Path.Combine(_dir, "tmp")))
+ {
+ new RemissWorkflow(cfg, printer, audit).Run(source, new Mottagning("Hud SU", "hsa-1"), temp);
+ }
+
+ Assert.True(string.IsNullOrEmpty(printer.Printer)); // SumatraPDF väljer -print-to-default
+ Assert.Contains("printer=", File.ReadAllText(Path.Combine(_dir, "audit.jsonl")));
+ }
+
[Fact]
public void Run_propagatesPrinterFailureAndAuditsTheStage()
{
@@ -80,6 +99,6 @@ public sealed class WorkflowTests : IDisposable
private sealed class ThrowingPrinter : IPrinter
{
- public void Print(string pdfPath, string printerName) => throw new InvalidOperationException("no printer");
+ public void Print(string pdfPath, string? printerName) => throw new InvalidOperationException("no printer");
}
}