Add Cc (Zusatzempfänger) recipient support for invoice emails
Lets an invoice carry an additional Cc recipient alongside the primary SendToEmail, sent via the ProcessWeb mailer API. Honors the Fuchs:Email:OverrideRecipient dev safety net: when the override is active, Cc is dropped entirely (only the single override inbox is ever reached), never leaked to it. - IComService.SendEmailAsync / ProcessWebComService: new optional `cc` parameter, included in the mailer payload only when non-empty. - FdsInvoiceData.RawInvoiceEmailCc + BuildInvoiceParams: reads `new.invoiceemailcc`, posts @SendToEmailCc. - IntranetController.Requests: reads SendToEmailCc from the loaded invoice row and passes it through on send/resend. - Fuchs_Database: SendToEmailCc column on fds__invoices, threaded through fds__createInvoice/fds__setInvoice/fds__getInvoice and the fds__tt__invoice_base/_core table types. - fis.req_txt_de.js: "Zusatzempfänger (CC)" field label. - Tests: ProcessWebComServiceTests (Cc included/omitted/dropped under override); SystemStatusServiceTests mocks updated for the new IComService.SendEmailAsync parameter. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -322,6 +322,46 @@ public class ProcessWebComServiceTests
|
||||
Assert.Equal("realcustomer@example.de", json["communication"]!["to"]!.ToString());
|
||||
}
|
||||
|
||||
// ── Cc recipient ("Zusatzempfänger") ─────────────────────────────────────────
|
||||
[Fact]
|
||||
public async Task SendEmailAsync_CcProvided_IsIncludedInPayload()
|
||||
{
|
||||
var handler = new StubHandler(HttpStatusCode.OK);
|
||||
var svc = CreateService(handler);
|
||||
|
||||
await svc.SendEmailAsync("inv_cc1", "Subject", "<p>hi</p>", "kunde@example.de", "Kunde",
|
||||
cc: "zusatz@example.de");
|
||||
|
||||
var json = JObject.Parse(handler.LastRequestBody!);
|
||||
Assert.Equal("zusatz@example.de", json["communication"]!["cc"]!.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendEmailAsync_CcOmitted_IsNullInPayload()
|
||||
{
|
||||
var handler = new StubHandler(HttpStatusCode.OK);
|
||||
var svc = CreateService(handler);
|
||||
|
||||
await svc.SendEmailAsync("inv_cc2", "Subject", "<p>hi</p>", "kunde@example.de", "Kunde");
|
||||
|
||||
var json = JObject.Parse(handler.LastRequestBody!);
|
||||
Assert.Equal(JTokenType.Null, json["communication"]!["cc"]!.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendEmailAsync_OverrideRecipientSet_DropsCcEntirely()
|
||||
{
|
||||
var handler = new StubHandler(HttpStatusCode.OK);
|
||||
var svc = CreateService(handler, overrideRecipient: "dev-inbox@example.test");
|
||||
|
||||
await svc.SendEmailAsync("inv_cc3", "Subject", "<p>hi</p>", "realcustomer@example.de", "Kunde",
|
||||
cc: "zusatz@example.de");
|
||||
|
||||
var json = JObject.Parse(handler.LastRequestBody!);
|
||||
Assert.Equal("dev-inbox@example.test", json["communication"]!["to"]!.ToString());
|
||||
Assert.Equal(JTokenType.Null, json["communication"]!["cc"]!.Type);
|
||||
}
|
||||
|
||||
// ── Override enforcement across every real appsettings*.json environment ───
|
||||
// Loads the ACTUAL Fuchs/appsettings*.json files the same way ASP.NET Core
|
||||
// layers them (base file + optional environment-specific overlay). These files
|
||||
|
||||
@@ -404,7 +404,7 @@ public class SystemStatusServiceTests
|
||||
|
||||
Assert.False(r.Sent);
|
||||
com.Verify(c => c.SendEmailAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Dictionary<string, byte[]>>()), Times.Never);
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Dictionary<string, byte[]>>(), It.IsAny<string?>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -413,9 +413,9 @@ public class SystemStatusServiceTests
|
||||
string? capturedHtml = null, capturedTo = null, capturedSubject = null;
|
||||
var com = new Mock<IComService>();
|
||||
com.Setup(c => c.SendEmailAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Dictionary<string, byte[]>?>()))
|
||||
.Callback<string, string, string, string, string, Dictionary<string, byte[]>?>(
|
||||
(_, subj, html, to, _, _) => { capturedSubject = subj; capturedHtml = html; capturedTo = to; })
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Dictionary<string, byte[]>?>(), It.IsAny<string?>()))
|
||||
.Callback<string, string, string, string, string, Dictionary<string, byte[]>?, string?>(
|
||||
(_, subj, html, to, _, _, _) => { capturedSubject = subj; capturedHtml = html; capturedTo = to; })
|
||||
.ReturnsAsync(true);
|
||||
var svc = Build(com: com);
|
||||
|
||||
@@ -436,7 +436,7 @@ public class SystemStatusServiceTests
|
||||
{
|
||||
var com = new Mock<IComService>();
|
||||
com.Setup(c => c.SendEmailAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Dictionary<string, byte[]>?>()))
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Dictionary<string, byte[]>?>(), It.IsAny<string?>()))
|
||||
.ReturnsAsync(true);
|
||||
var svc = Build(com: com, email: new FuchsEmailSettings { OverrideRecipient = "safety@example.test" });
|
||||
|
||||
@@ -453,7 +453,7 @@ public class SystemStatusServiceTests
|
||||
{
|
||||
var com = new Mock<IComService>();
|
||||
com.Setup(c => c.SendEmailAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Dictionary<string, byte[]>?>()))
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Dictionary<string, byte[]>?>(), It.IsAny<string?>()))
|
||||
.ReturnsAsync(false);
|
||||
var svc = Build(com: com);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user