added improvements on set proces and minor other improvements
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction for outbound communication: email and SMS.
|
||||
/// Backed by the ProcessWeb Mailer API (POST /api/mailer?fn=push_com).
|
||||
/// Backed by the ProcessWeb Mailer API (POST {BaseUrl}/mailer/push_com).
|
||||
/// </summary>
|
||||
public interface IComService
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Fuchs.intranet;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -247,10 +247,53 @@ public sealed class InvoiceDraftEditService : IInvoiceDraftService
|
||||
block_net = session.Sums.NetByBlock
|
||||
},
|
||||
validation = session.ValidationMessages.Select(v => new { field = v.Field, severity = v.Severity, message = v.Message }),
|
||||
historyCount = session.History.Count
|
||||
historyCount = session.History.Count,
|
||||
notes = BuildNotes(session),
|
||||
setDisplay = BuildSetDisplay(session)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authoritative per-item set-pricing display flags (ADR 0006/set-pricing): for every block
|
||||
/// that contains a "set" item, runs the same <see cref="InvoiceSetPricing.Build"/> the PDF
|
||||
/// uses (keyed by <c>admin.setmode</c>) and returns a flat map of item id → whether its
|
||||
/// price/total should be shown and whether it is a set header. The online editor applies
|
||||
/// this to the rendered rows so it always matches the PDF without duplicating the pricing
|
||||
/// rules client-side.
|
||||
/// </summary>
|
||||
private static Dictionary<string, object> BuildSetDisplay(InvoiceDraftSession session)
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
var mode = InvoiceSetPricing.ParseMode(Str(session.Admin["setmode"]));
|
||||
foreach (var blockTok in session.Req)
|
||||
{
|
||||
if (blockTok is not JObject block || block["items"] is not JArray itemsArr) continue;
|
||||
List<Dictionary<string, object?>>? items = itemsArr.ToObject<List<Dictionary<string, object?>>>();
|
||||
if (items == null || !InvoiceSetPricing.ContainsSets(items)) continue;
|
||||
|
||||
foreach (var line in InvoiceSetPricing.Build(items, mode))
|
||||
{
|
||||
if (string.IsNullOrEmpty(line.Id)) continue;
|
||||
result[line.Id] = new { p = line.ShowPrice, h = line.IsSetHeader };
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the backend-authoritative notice paragraphs (see <see cref="FuchsPdf.BuildInvoiceNotes"/>)
|
||||
/// for the editor's read-only preview — the same text the PDF will show, generated from the
|
||||
/// same builder, so the two can never drift apart.
|
||||
/// </summary>
|
||||
private IEnumerable<object> BuildNotes(InvoiceDraftSession session)
|
||||
{
|
||||
var fds = BuildFdsData(session);
|
||||
fds.InvoiceRegistration = SynthesizeRegistration(session);
|
||||
bool p13b = AsBool(session.Admin["p13b"]);
|
||||
return FuchsPdf.BuildInvoiceNotes(new FuchsPdf.FdsTextBlocks(), fds, p13b)
|
||||
.Select(n => new { text = n.Text, style = n.Style });
|
||||
}
|
||||
|
||||
public IReadOnlyList<ChangeHistoryEntry> GetHistory(string token) =>
|
||||
_cache.Get(token)?.History ?? (IReadOnlyList<ChangeHistoryEntry>)Array.Empty<ChangeHistoryEntry>();
|
||||
|
||||
@@ -385,7 +428,9 @@ public sealed class InvoiceDraftEditService : IInvoiceDraftService
|
||||
var tokens = new List<string>();
|
||||
if (AsBool(session.Admin["p13b"])) tokens.Add("§13b");
|
||||
string setmode = Str(session.Admin["setmode"]).Trim().ToLowerInvariant();
|
||||
if (setmode is "itemprices" or "setonly") tokens.Add("setmode:" + setmode);
|
||||
// Mirrors FdsInvoiceData.BuildInvoiceOptions: persist any explicitly-chosen mode
|
||||
// (including the default "setprice") so it is distinguishable from "never touched".
|
||||
if (setmode is "setprice" or "itemprices" or "setonly") tokens.Add("setmode:" + setmode);
|
||||
return string.Join(",", tokens);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Fuchs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Outbound communication service backed by the ProcessWeb Mailer API
|
||||
/// (POST https://api.processweb.de/api/mailer?fn=push_com).
|
||||
/// (POST https://api.processweb.de/mailer/push_com).
|
||||
/// When <c>ProcessWebComSettings.Enabled</c> is <c>false</c> the service
|
||||
/// only logs the intended communication without calling the API.
|
||||
/// </summary>
|
||||
@@ -105,10 +105,9 @@ public class ProcessWebComService : IComService
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
var payload = new
|
||||
var communication = new
|
||||
{
|
||||
comType = "email",
|
||||
recipient = email,
|
||||
to = email,
|
||||
subject,
|
||||
body,
|
||||
attachments = attachmentPayload
|
||||
@@ -117,7 +116,7 @@ public class ProcessWebComService : IComService
|
||||
_logger.LogDebug("SendEmailAsync ref={Reference} to={Email} attachments={Count}",
|
||||
reference, email, attachmentPayload.Length);
|
||||
|
||||
var (ok, responseBody) = await PostToApiAsync("push_com", payload);
|
||||
var (ok, responseBody) = await PostToApiAsync(reference, "e", communication);
|
||||
if (ok)
|
||||
{
|
||||
success = true;
|
||||
@@ -166,14 +165,13 @@ public class ProcessWebComService : IComService
|
||||
|
||||
try
|
||||
{
|
||||
var payload = new
|
||||
var communication = new
|
||||
{
|
||||
comType = "sms",
|
||||
recipient = mobile,
|
||||
body = message
|
||||
to = mobile,
|
||||
body = message
|
||||
};
|
||||
|
||||
var (ok, responseBody) = await PostToApiAsync("push_com", payload);
|
||||
var (ok, responseBody) = await PostToApiAsync(mobile, "s", communication);
|
||||
if (ok)
|
||||
{
|
||||
FuchsTelemetry.SmsSent.Add(1);
|
||||
@@ -193,10 +191,20 @@ public class ProcessWebComService : IComService
|
||||
|
||||
// ── Private helpers ────────────────────────────────────────────────────────
|
||||
|
||||
private async Task<(bool ok, string body)> PostToApiAsync(string fn, object payload)
|
||||
private async Task<(bool ok, string body)> PostToApiAsync(string reference, string comtype, object communication)
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient("ProcessWebMailer");
|
||||
var json = JsonConvert.SerializeObject(payload);
|
||||
|
||||
// The Mailer API expects a fixed envelope: { serverid, comtype, communication, reference }.
|
||||
// "communication" carries the actual channel-specific payload (to/subject/body/attachments).
|
||||
var payload = new
|
||||
{
|
||||
serverid = _settings.ServerId,
|
||||
comtype,
|
||||
communication,
|
||||
reference
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(payload);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
string credentials = Convert.ToBase64String(
|
||||
@@ -204,7 +212,7 @@ public class ProcessWebComService : IComService
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Basic", credentials);
|
||||
|
||||
var response = await client.PostAsync($"{_settings.BaseUrl}/api/mailer?fn={fn}", content);
|
||||
var response = await client.PostAsync($"{_settings.BaseUrl}/mailer/push_com", content);
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
return (response.IsSuccessStatusCode, responseBody);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ public class ProcessWebComSettings
|
||||
/// <summary>API token used for HTTP Basic authentication.</summary>
|
||||
public string Token { get; set; } = "";
|
||||
|
||||
/// <summary>Server identifier assigned by ProcessWeb, sent as "serverid" in every push_com call.</summary>
|
||||
public string ServerId { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// When <c>false</c> (default) the service is disabled and only logs the
|
||||
/// intended communication without actually calling the API.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using Fuchs.intranet;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MigraDoc.DocumentObjectModel;
|
||||
@@ -159,10 +159,24 @@ public sealed class ReminderDraftEditService : IReminderDraftService
|
||||
amount_open = session.Sums.AmountOpen
|
||||
},
|
||||
validation = session.ValidationMessages.Select(v => new { field = v.Field, severity = v.Severity, message = v.Message }),
|
||||
historyCount = session.History.Count
|
||||
historyCount = session.History.Count,
|
||||
notes = BuildNotes(session)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the backend-authoritative intro/closing notice paragraphs (see
|
||||
/// <see cref="FuchsPdf.BuildReminderNotes"/>) for the editor's read-only preview — the same
|
||||
/// text the PDF will show, generated from the same builder, so the two can never drift apart.
|
||||
/// </summary>
|
||||
private object BuildNotes(ReminderDraftSession session)
|
||||
{
|
||||
var fds = BuildReminderData(session);
|
||||
fds.ReminderRegistration = SynthesizeRegistration(session);
|
||||
var (intro, closing) = FuchsPdf.BuildReminderNotes(new FuchsPdf.FdsTextBlocks(), fds);
|
||||
return new { intro, closing };
|
||||
}
|
||||
|
||||
public IReadOnlyList<ChangeHistoryEntry> GetHistory(string token) =>
|
||||
_cache.Get(token)?.History ?? (IReadOnlyList<ChangeHistoryEntry>)Array.Empty<ChangeHistoryEntry>();
|
||||
|
||||
@@ -250,6 +264,8 @@ public sealed class ReminderDraftEditService : IReminderDraftService
|
||||
["SendToAddress"] = Str(session.New["invoiceaddress"]),
|
||||
["SendToEmail"] = Str(session.New["invoiceemail"]),
|
||||
["InvoiceId"] = invoiceId,
|
||||
["amount"] = session.Sums.AmountTotal,
|
||||
["amount_payed"] = session.Sums.AmountPayed,
|
||||
["amount_open"] = session.Sums.AmountOpen,
|
||||
["PaymentTerm"] = Str(session.Rem["paymentterm"]),
|
||||
["invoices"] = invoices,
|
||||
|
||||
Reference in New Issue
Block a user