diff --git a/Fuchs.Tests/ProcessWebComServiceTests.cs b/Fuchs.Tests/ProcessWebComServiceTests.cs
index 28d2248..0792a5f 100644
--- a/Fuchs.Tests/ProcessWebComServiceTests.cs
+++ b/Fuchs.Tests/ProcessWebComServiceTests.cs
@@ -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", "
hi
", "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", "hi
", "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", "hi
", "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
diff --git a/Fuchs.Tests/SystemStatusServiceTests.cs b/Fuchs.Tests/SystemStatusServiceTests.cs
index b02b954..741866e 100644
--- a/Fuchs.Tests/SystemStatusServiceTests.cs
+++ b/Fuchs.Tests/SystemStatusServiceTests.cs
@@ -404,7 +404,7 @@ public class SystemStatusServiceTests
Assert.False(r.Sent);
com.Verify(c => c.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(),
- It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never);
+ It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Never);
}
[Fact]
@@ -413,9 +413,9 @@ public class SystemStatusServiceTests
string? capturedHtml = null, capturedTo = null, capturedSubject = null;
var com = new Mock();
com.Setup(c => c.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(),
- It.IsAny(), It.IsAny(), It.IsAny?>()))
- .Callback?>(
- (_, subj, html, to, _, _) => { capturedSubject = subj; capturedHtml = html; capturedTo = to; })
+ It.IsAny(), It.IsAny(), It.IsAny?>(), It.IsAny()))
+ .Callback?, 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();
com.Setup(c => c.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(),
- It.IsAny(), It.IsAny(), It.IsAny?>()))
+ It.IsAny(), It.IsAny(), It.IsAny?>(), It.IsAny()))
.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();
com.Setup(c => c.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(),
- It.IsAny(), It.IsAny(), It.IsAny?>()))
+ It.IsAny(), It.IsAny(), It.IsAny?>(), It.IsAny()))
.ReturnsAsync(false);
var svc = Build(com: com);
diff --git a/Fuchs/Controllers/IntranetController.Requests.cs b/Fuchs/Controllers/IntranetController.Requests.cs
index 8a90277..ac0479e 100644
--- a/Fuchs/Controllers/IntranetController.Requests.cs
+++ b/Fuchs/Controllers/IntranetController.Requests.cs
@@ -324,6 +324,7 @@ public partial class IntranetController
Security: DbSec, options: SqlOpt(fn, id, code));
frdic = dtset.Table("inv").FirstRow.toObjectDictionary();
string email = frdic.nz("SendToEmail", "");
+ string emailCc = frdic.nz("SendToEmailCc", "");
if (!string.IsNullOrEmpty(email) && filebyte.Length > 0)
{
var inv = new Dictionary { [frdic.nz("DocumentName")] = filebyte };
@@ -334,7 +335,7 @@ public partial class IntranetController
invId, email.Trim(), UserAccountID);
bool sent = await _comService.SendEmailAsync(
$"inv_{invId}", $"Sanit\u00e4rFuchs - {frdic.nz("DocumentName")}",
- body, email.Trim(), "", inv);
+ body, email.Trim(), "", inv, string.IsNullOrEmpty(emailCc) ? null : emailCc.Trim());
if (sent)
{
await _events.InvoiceSentToCustomerAsync(fdInv, email.Trim(), UserAccountID);
@@ -425,6 +426,7 @@ public partial class IntranetController
var fdInv = await _invoices.LoadInvoiceAsync(invId, UserAccountID, DbSec);
byte[] filebyte = await _invoices.RenderInvoicePdfBytesAsync(fdInv, fdInv.IsDraft);
string email = frdic.nz("SendToEmail", "");
+ string emailCc = frdic.nz("SendToEmailCc", "");
if (!string.IsNullOrEmpty(email) && filebyte.Length > 0)
{
double bal = Convert.ToDouble(frdic.no("InvoiceBalance", 0));
@@ -432,7 +434,8 @@ public partial class IntranetController
bool sent = await _comService.SendEmailAsync(
$"inv_{invId}", $"Sanit\u00e4rFuchs - {frdic.nz("DocumentName")}",
BuildInvoiceBody(bal, terms), email.Trim(), "",
- new Dictionary { [frdic.nz("DocumentName")] = filebyte });
+ new Dictionary { [frdic.nz("DocumentName")] = filebyte },
+ string.IsNullOrEmpty(emailCc) ? null : emailCc.Trim());
if (sent)
await _events.InvoiceSentToCustomerAsync(fdInv, email.Trim(), UserAccountID, resent: true);
else
diff --git a/Fuchs/Services/IComService.cs b/Fuchs/Services/IComService.cs
index d7880ad..5b419cc 100644
--- a/Fuchs/Services/IComService.cs
+++ b/Fuchs/Services/IComService.cs
@@ -15,9 +15,10 @@ public interface IComService
/// Recipient email address.
/// Recipient display name.
/// Optional file attachments (filename → content).
+ /// Optional Cc recipient (e.g. a customer's "Zusatzempfänger").
/// true when the email was accepted successfully.
Task SendEmailAsync(string reference, string subject, string html,
- string email, string name, Dictionary? attachments = null);
+ string email, string name, Dictionary? attachments = null, string? cc = null);
///
/// Sends an SMS message.
diff --git a/Fuchs/Services/InvoiceService.cs b/Fuchs/Services/InvoiceService.cs
index 7d7463c..c86a117 100644
--- a/Fuchs/Services/InvoiceService.cs
+++ b/Fuchs/Services/InvoiceService.cs
@@ -135,13 +135,13 @@ public class InvoiceService : IInvoiceService
var sqlParts = new List { "DECLARE @Id varchar(10);" };
if (!change)
{
- sqlParts.Add("EXECUTE [dbo].[fds__createInvoice] @InvoiceType, @InvoiceTitle, @InvoiceBalance, @InvoiceBalance_net, @InvoiceVAT_net1, @InvoiceVAT_1, @PaymentTerm, @CustomerId, @SendToAddress, @SendToEmail, @ProvisionPeriod, @CustomValues, @authuser, @Id OUTPUT, @SendToAddressJson;");
+ sqlParts.Add("EXECUTE [dbo].[fds__createInvoice] @InvoiceType, @InvoiceTitle, @InvoiceBalance, @InvoiceBalance_net, @InvoiceVAT_net1, @InvoiceVAT_1, @PaymentTerm, @CustomerId, @SendToAddress, @SendToEmail, @SendToEmailCc, @ProvisionPeriod, @CustomValues, @authuser, @Id OUTPUT, @SendToAddressJson;");
sqlParts.Add("EXECUTE [dbo].[fds__createInvoice_Details] @Id, @InvoiceService_net, @InvoiceService_VAT, @InvoiceOptions, @authuser;");
}
else
{
pl.Add(SQL_VarChar("@InvId", invId));
- sqlParts.Add("EXECUTE [dbo].[fds__setInvoice] @InvId, @InvoiceType, @InvoiceTitle, @InvoiceBalance, @InvoiceBalance_net, @InvoiceVAT_net1, @InvoiceVAT_1, @PaymentTerm, @CustomerId, @SendToAddress, @SendToEmail, @ProvisionPeriod, @CustomValues, @authuser, @Id OUTPUT, @SendToAddressJson;");
+ sqlParts.Add("EXECUTE [dbo].[fds__setInvoice] @InvId, @InvoiceType, @InvoiceTitle, @InvoiceBalance, @InvoiceBalance_net, @InvoiceVAT_net1, @InvoiceVAT_1, @PaymentTerm, @CustomerId, @SendToAddress, @SendToEmail, @SendToEmailCc, @ProvisionPeriod, @CustomValues, @authuser, @Id OUTPUT, @SendToAddressJson;");
sqlParts.Add("EXECUTE [dbo].[fds__createInvoice_Details] @Id, @InvoiceService_net, @InvoiceService_VAT, @InvoiceOptions, @authuser;");
}
if (invoice.RawProvisionLocation.Length > 0)
diff --git a/Fuchs/Services/ProcessWebComService.cs b/Fuchs/Services/ProcessWebComService.cs
index c7fea40..648d145 100644
--- a/Fuchs/Services/ProcessWebComService.cs
+++ b/Fuchs/Services/ProcessWebComService.cs
@@ -46,10 +46,11 @@ public class ProcessWebComService : IComService
}
public async Task SendEmailAsync(string reference, string subject, string html,
- string email, string name, Dictionary? attachments = null)
+ string email, string name, Dictionary? attachments = null, string? cc = null)
{
using var act = FuchsTelemetry.StartActivity("email.send");
act?.SetTag("fuchs.email.ref", reference);
+ cc = string.IsNullOrWhiteSpace(cc) ? null : cc.Trim();
string overrideRecipient = _emailSettings.OverrideRecipient ?? "";
if (!string.IsNullOrWhiteSpace(overrideRecipient))
@@ -58,12 +59,13 @@ public class ProcessWebComService : IComService
// redirect every outbound email to a single controlled inbox, so a locally
// enabled mailer can never reach a real tenant-owner or end-customer.
_logger.LogWarning(
- "SendEmailAsync: recipient override active for ref {Reference} – redirecting from '{OriginalEmail}' to '{OverrideRecipient}'",
- reference, email, overrideRecipient);
+ "SendEmailAsync: recipient override active for ref {Reference} – redirecting from '{OriginalEmail}' (cc='{OriginalCc}') to '{OverrideRecipient}'",
+ reference, email, cc ?? "", overrideRecipient);
act?.SetTag("fuchs.email.overridden", true);
act?.SetTag("fuchs.email.original_recipient", email);
subject = $"[DEV \u2192 {email}] {subject}";
email = overrideRecipient;
+ cc = null;
}
if (!IsValidEmail(email))
@@ -111,13 +113,14 @@ public class ProcessWebComService : IComService
var communication = new
{
to = email,
+ cc,
subject,
body,
attachments = attachmentPayload
};
- _logger.LogDebug("SendEmailAsync ref={Reference} to={Email} attachments={Count}",
- reference, email, attachmentPayload.Length);
+ _logger.LogDebug("SendEmailAsync ref={Reference} to={Email} cc={Cc} attachments={Count}",
+ reference, email, cc ?? "", attachmentPayload.Length);
var (ok, status, responseBody) = await PostToApiAsync(reference, "e", communication);
if (ok)
diff --git a/Fuchs/code/FdsInvoiceData.cs b/Fuchs/code/FdsInvoiceData.cs
index 96a20b0..7a35455 100644
--- a/Fuchs/code/FdsInvoiceData.cs
+++ b/Fuchs/code/FdsInvoiceData.cs
@@ -133,6 +133,8 @@ public class FdsInvoiceData
// -- Raw form properties (used by parameter mapping) -----------------------
internal string RawInvoiceAddress => NewValues?.nz("invoiceaddress") ?? "";
internal string RawInvoiceEmail => NewValues?.nz("invoiceemail").Trim() ?? "";
+ /// Additional recipient ("Zusatzempfänger") sent as Cc alongside the primary invoice email.
+ internal string RawInvoiceEmailCc => NewValues?.nz("invoiceemailcc").Trim() ?? "";
internal string RawCustomValues => NewValues?.nz("CustomValues").Trim() ?? "";
internal string RawProvisionPeriod => NewValues?.nz("provisionperiod") ?? "";
internal string[] RawProvisionLocation =>
@@ -179,6 +181,7 @@ public class FdsInvoiceData
SQL_BigInt("@CustomerId", Admin?.nz("customerid") ?? ""),
SQL_VarChar("@SendToAddress", RawInvoiceAddress),
SQL_NVarChar("@SendToEmail", RawInvoiceEmail),
+ SQL_NVarChar("@SendToEmailCc", RawInvoiceEmailCc, dbNull_IfEmpty: true),
SQL_NVarChar("@ProvisionPeriod", RawProvisionPeriod, dbNull_IfEmpty: true),
SQL_NVarChar("@CustomValues", RawCustomValues, dbNull_IfEmpty: true),
SQL_NVarChar("@SendToAddressJson", SendToAddressJsonForPersist(), dbNull_IfEmpty: true),
diff --git a/Fuchs/js/intranet/modules/fis.req_txt_de.js b/Fuchs/js/intranet/modules/fis.req_txt_de.js
index f52f98c..79094a0 100644
--- a/Fuchs/js/intranet/modules/fis.req_txt_de.js
+++ b/Fuchs/js/intranet/modules/fis.req_txt_de.js
@@ -65,6 +65,7 @@
invoiceaddress: 'Adresse'
, loc: 'Leistungsort / Lieferadresse'
, invoiceemail: 'Email'
+ , invoiceemailcc: 'Zusatzempfänger (CC)'
}
}, $rcol = {
diff --git a/Fuchs_Database/dbo/Stored Procedures/fds__createInvoice.sql b/Fuchs_Database/dbo/Stored Procedures/fds__createInvoice.sql
index 1ffb679..2b80127 100644
--- a/Fuchs_Database/dbo/Stored Procedures/fds__createInvoice.sql
+++ b/Fuchs_Database/dbo/Stored Procedures/fds__createInvoice.sql
@@ -14,7 +14,8 @@ CREATE PROCEDURE [dbo].[fds__createInvoice]
@CustomerId bigint,
@SendToAddress nvarchar(1000),
@SendToEmail nvarchar(255),
- @ProvisionPeriod varchar(50),
+ @SendToEmailCc nvarchar(255) = NULL,
+ @ProvisionPeriod varchar(50),
@CustomValues nvarchar(max),
@authuser varchar(25),
@Id varchar(10) OUT,
@@ -56,6 +57,7 @@ BEGIN
[CustomerId] [bigint] NULL,
[SendToAddress] [nvarchar](1000) NULL,
[SendToEmail] [nvarchar](255) NULL,
+ [SendToEmailCc] [nvarchar](255) NULL,
[ProvisionPeriod] [varchar](50) NULL,
[ProvisionLocation] [nvarchar](1000) NULL,
[PaymentStatus] [char](1) NULL,
@@ -100,6 +102,7 @@ BEGIN
,[CustomerId]
,[SendToAddress]
,[SendToEmail]
+ ,[SendToEmailCc]
,[ProvisionPeriod]
,[ProvisionLocation]
,[PaymentStatus]
@@ -140,6 +143,7 @@ BEGIN
,@CustomerId
,@SendToAddress
,@SendToEmail
+ ,@SendToEmailCc
,@ProvisionPeriod
,NULL --[ProvisionLocation]
,NULL --[PaymentStatus]
diff --git a/Fuchs_Database/dbo/Stored Procedures/fds__getInvoice.sql b/Fuchs_Database/dbo/Stored Procedures/fds__getInvoice.sql
index 9d3bd22..8a80256 100644
--- a/Fuchs_Database/dbo/Stored Procedures/fds__getInvoice.sql
+++ b/Fuchs_Database/dbo/Stored Procedures/fds__getInvoice.sql
@@ -36,6 +36,7 @@ BEGIN
,[CustomerId]
,[SendToAddress]
,[SendToEmail]
+ ,[SendToEmailCc]
,[ProvisionPeriod]
,[ProvisionLocation]
,[PaymentStatus]
diff --git a/Fuchs_Database/dbo/Stored Procedures/fds__setInvoice.sql b/Fuchs_Database/dbo/Stored Procedures/fds__setInvoice.sql
index 6cab404..fd60e05 100644
--- a/Fuchs_Database/dbo/Stored Procedures/fds__setInvoice.sql
+++ b/Fuchs_Database/dbo/Stored Procedures/fds__setInvoice.sql
@@ -15,7 +15,8 @@ CREATE PROCEDURE [dbo].[fds__setInvoice]
@CustomerId bigint,
@SendToAddress nvarchar(1000),
@SendToEmail nvarchar(255),
- @ProvisionPeriod varchar(50),
+ @SendToEmailCc nvarchar(255) = NULL,
+ @ProvisionPeriod varchar(50),
@CustomValues nvarchar(max),
@authuser varchar(25),
@Id varchar(10) OUT,
@@ -57,6 +58,7 @@ BEGIN
[CustomerId] [bigint] NULL,
[SendToAddress] [nvarchar](1000) NULL,
[SendToEmail] [nvarchar](255) NULL,
+ [SendToEmailCc] [nvarchar](255) NULL,
[ProvisionPeriod] [varchar](50) NULL,
[ProvisionLocation] [nvarchar](1000) NULL,
[PaymentStatus] [char](1) NULL,
@@ -98,6 +100,7 @@ BEGIN
,@CustomerId
,@SendToAddress
,@SendToEmail
+ ,@SendToEmailCc
,@ProvisionPeriod
,NULL --[ProvisionLocation]
,NULL --[PaymentStatus]
@@ -130,6 +133,7 @@ BEGIN
,[CustomerId]
,[SendToAddress]
,[SendToEmail]
+ ,[SendToEmailCc]
,[ProvisionPeriod]
,[ProvisionLocation]
,[PaymentStatus]
@@ -164,6 +168,7 @@ BEGIN
,[CustomerId] = SOURCE.[CustomerId]
,[SendToAddress] = SOURCE.[SendToAddress]
,[SendToEmail] = SOURCE.[SendToEmail]
+ ,[SendToEmailCc] = SOURCE.[SendToEmailCc]
,[ProvisionPeriod] = SOURCE.[ProvisionPeriod]
,[ProvisionLocation] = SOURCE.[ProvisionLocation]
,[CustomValues] = SOURCE.[CustomValues]
diff --git a/Fuchs_Database/dbo/Tables/fds__invoices.sql b/Fuchs_Database/dbo/Tables/fds__invoices.sql
index 6fa09c9..f9d84a9 100644
--- a/Fuchs_Database/dbo/Tables/fds__invoices.sql
+++ b/Fuchs_Database/dbo/Tables/fds__invoices.sql
@@ -16,6 +16,7 @@
[CustomerId] BIGINT NULL,
[SendToAddress] NVARCHAR (1000) NULL,
[SendToEmail] NVARCHAR (255) NULL,
+ [SendToEmailCc] NVARCHAR (255) NULL,
[ProvisionPeriod] VARCHAR (50) NULL,
[ProvisionLocation] NVARCHAR (1000) NULL,
[PaymentStatus] CHAR (1) NULL,
diff --git a/Fuchs_Database/dbo/User Defined Types/fds__tt__invoice_base.sql b/Fuchs_Database/dbo/User Defined Types/fds__tt__invoice_base.sql
index 9472413..1ada4c4 100644
--- a/Fuchs_Database/dbo/User Defined Types/fds__tt__invoice_base.sql
+++ b/Fuchs_Database/dbo/User Defined Types/fds__tt__invoice_base.sql
@@ -16,6 +16,7 @@
[CustomerId] BIGINT NULL,
[SendToAddress] NVARCHAR (1000) NULL,
[SendToEmail] NVARCHAR (255) NULL,
+ [SendToEmailCc] NVARCHAR (255) NULL,
[ProvisionPeriod] VARCHAR (50) NULL,
[ProvisionLocation] NVARCHAR (1000) NULL,
[PaymentStatus] CHAR (1) NULL,
diff --git a/Fuchs_Database/dbo/User Defined Types/fds__tt__invoice_core.sql b/Fuchs_Database/dbo/User Defined Types/fds__tt__invoice_core.sql
index 6e53ac6..a7b0102 100644
--- a/Fuchs_Database/dbo/User Defined Types/fds__tt__invoice_core.sql
+++ b/Fuchs_Database/dbo/User Defined Types/fds__tt__invoice_core.sql
@@ -16,6 +16,7 @@
[CustomerId] BIGINT NULL,
[SendToAddress] NVARCHAR (1000) NULL,
[SendToEmail] NVARCHAR (255) NULL,
+ [SendToEmailCc] NVARCHAR (255) NULL,
[ProvisionPeriod] VARCHAR (50) NULL,
[ProvisionLocation] NVARCHAR (1000) NULL,
[PaymentStatus] CHAR (1) NULL,