Refactor code structure for improved readability and maintainability
Playwright Tests / test (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
This commit is contained in:
@@ -6,6 +6,7 @@ using Fuchs.Observability;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MigraDoc.DocumentObjectModel;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OCORE.security;
|
||||
using OCORE.SQL;
|
||||
using static OCORE.commons;
|
||||
@@ -48,11 +49,13 @@ public class InvoiceService : IInvoiceService
|
||||
var pl = new List<SqlParameter>
|
||||
{
|
||||
SQL_VarChar("@authuser", userAccountId),
|
||||
SQL_VarChar("@Id", id),
|
||||
SQL_Bit("@includefile", false)
|
||||
SQL_VarChar("@Id", id)
|
||||
};
|
||||
// fds__getInvoice only takes @Id and @authuser (no @includefile) - passing an extra
|
||||
// parameter makes SQL Server throw "too many arguments", which previously left
|
||||
// InvoiceRegistration empty (Id "") and silently broke downstream file storage/emailing.
|
||||
var dset = await getSQLDataSet_async(
|
||||
"EXECUTE [dbo].[fds__getInvoice] @Id, @includefile, @authuser;",
|
||||
"EXECUTE [dbo].[fds__getInvoice] @Id, @authuser;",
|
||||
Conn, pl, tablenames: new[] { "admin", "inv", "req", "itm" },
|
||||
Security: dbSec, options: new FIS_SQLOptions());
|
||||
if (!string.IsNullOrEmpty(dset.Exception))
|
||||
@@ -93,16 +96,113 @@ public class InvoiceService : IInvoiceService
|
||||
}
|
||||
|
||||
var invdset = await getSQLDataSet_async(string.Join("\n", sqlParts),
|
||||
Conn, pl, tablenames: new[] { "inv", "det", "req", "itm" },
|
||||
Conn, pl, tablenames: new[] { "inv", "det" },
|
||||
Security: dbSec, options: new FIS_SQLOptions());
|
||||
if (!string.IsNullOrEmpty(invdset.Exception))
|
||||
_logger.LogError("RegisterInvoiceAsync sql exception: {Ex}", invdset.Exception);
|
||||
|
||||
invoice.InvoiceRegistration = new GenericObjectDictionary(invdset.Table("inv").FirstRow.toObjectDictionary());
|
||||
_logger.LogInformation("RegisterInvoiceAsync registered id={Id} (change={Change})", invoice.Id, change);
|
||||
|
||||
if (!string.IsNullOrEmpty(invoice.Id) && invoice.Req != null)
|
||||
await PersistInvoiceLineItemsAsync(invoice, userAccountId, dbSec);
|
||||
|
||||
return invoice;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists the invoice's service-request groupings and their line items
|
||||
/// (<c>fds__invoice_servicerequests</c> / <c>fds__invoice_items</c>). These are what
|
||||
/// <c>fds__getInvoice</c> reads back when a draft is reloaded (e.g. after "Zwischenstand
|
||||
/// speichern" or reopening a draft from the invoice list) — the preview/PDF itself renders
|
||||
/// straight from the posted JSON, so without this the preview looked fine right after
|
||||
/// posting but a reloaded draft showed no steps/positions at all (nothing had ever been
|
||||
/// written for the service requests/items, only the invoice header row).
|
||||
/// The whole set is cleared and rewritten on every call — simpler and safer than diffing —
|
||||
/// mirroring the legacy <c>fds__invoice_data.RegisterInvoice_V1</c> (VB) behaviour.
|
||||
/// </summary>
|
||||
private async Task PersistInvoiceLineItemsAsync(FdsInvoiceData invoice, string userAccountId, DatabaseSecurity dbSec)
|
||||
{
|
||||
string invoiceId = invoice.Id;
|
||||
var clearReqPl = new List<SqlParameter> { SQL_VarChar("@authuser", userAccountId), SQL_VarChar("@Id", invoiceId) };
|
||||
await setSQLValue_async("EXECUTE [dbo].[fds__remInvoice_ServiceRequests] @Id, @authuser;",
|
||||
Conn, clearReqPl, Security: dbSec, options: new FIS_SQLOptions());
|
||||
|
||||
var itemsTable = (await getSQLDatatable_async(
|
||||
"SELECT TOP(0) * FROM [dbo].[fds__invoice_items];",
|
||||
Conn, Security: dbSec, options: new FIS_SQLOptions())).DataTable;
|
||||
|
||||
int sortOrder = -1;
|
||||
foreach (var reqEntry in invoice.Req!)
|
||||
{
|
||||
sortOrder++; // 0-based, matching legacy fds__invoice_data.RegisterInvoice_V1 (VB)'s "ri" loop index
|
||||
var rdic = new GenericObjectDictionary(reqEntry);
|
||||
|
||||
float vnet = 0;
|
||||
var valueNetParam = TryCastSingle(rdic.getItem("netval"), ref vnet)
|
||||
? SQL_Float("@value_net", vnet)
|
||||
: SQL_Float("@value_net", stringvalue: rdic.nz("netval"));
|
||||
var reqPl = new List<SqlParameter>
|
||||
{
|
||||
SQL_VarChar("@authuser", userAccountId),
|
||||
SQL_VarChar("@InvId", invoiceId),
|
||||
SQL_BigInt("@mfr__servicerequest", rdic.nz("Id")),
|
||||
SQL_NVarChar("@title", rdic.nz("text").ne(rdic.nz("nme"))),
|
||||
valueNetParam,
|
||||
SQL_Int("@SortOrder", sortOrder)
|
||||
};
|
||||
var reqDt = await getSQLDatatable_async(
|
||||
"EXECUTE [dbo].[fds__createInvoice_ServiceRequest] @InvId, @mfr__servicerequest, @title, @value_net, @SortOrder;",
|
||||
Conn, reqPl, Security: dbSec, options: new FIS_SQLOptions());
|
||||
if (!string.IsNullOrEmpty(reqDt.Exception))
|
||||
_logger.LogError("PersistInvoiceLineItemsAsync: fds__createInvoice_ServiceRequest failed for invoice {Id}: {Ex}", invoiceId, reqDt.Exception);
|
||||
string invRqId = reqDt.FirstRow.nz("Id");
|
||||
if (string.IsNullOrEmpty(invRqId)) continue;
|
||||
|
||||
if (!rdic.TryGetValue("itm", out var itmObj) || itmObj is not JArray itmArray) continue;
|
||||
int itemSort = 0;
|
||||
foreach (var tok in itmArray)
|
||||
{
|
||||
itemSort++;
|
||||
if (tok is not JObject itmJson) continue;
|
||||
var itmDic = new GenericObjectDictionary(itmJson.ToObject<Dictionary<string, object>>()!);
|
||||
if ((itmDic.nz("id") ?? "") == "" && (itmDic.nz("typ") ?? "") == "" && (itmDic.nz("t") ?? "") == "") continue;
|
||||
|
||||
var row = itemsTable.NewRow();
|
||||
row["InvId"] = invoiceId;
|
||||
row["InvRqId"] = invRqId;
|
||||
if (long.TryParse(itmDic.nz("id"), out long mfrItem)) row["mfr__item"] = mfrItem;
|
||||
row["Type"] = itmDic.nz("typ");
|
||||
row["Position"] = itmDic.nz("p");
|
||||
row["Quantity"] = itmDic.nz("q");
|
||||
row["Text"] = itmDic.nz("t");
|
||||
float v = 0;
|
||||
if (TryCastSingle(itmDic.getItem("v"), ref v)) row["value"] = (decimal)v;
|
||||
float vt = 0;
|
||||
if (TryCastSingle(itmDic.getItem("vt"), ref vt)) row["value_total"] = (decimal)vt;
|
||||
float vat = 0;
|
||||
if (TryCastSingle(itmDic.nz("vat").Replace("%", ""), ref vat)) row["vat"] = (decimal)vat;
|
||||
float vs = 0;
|
||||
if (TryCastSingle(itmDic.getItem("vs"), ref vs)) row["value_service"] = (decimal)vs;
|
||||
row["det"] = itmDic.no("det", false) is true;
|
||||
row["SortOrder"] = (byte)Math.Clamp(itemSort, 0, 255);
|
||||
itemsTable.Rows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
await setSQLValue_async("EXECUTE [dbo].[fds__remInvoice_Items] @Id, @authuser;",
|
||||
Conn, new List<SqlParameter> { SQL_VarChar("@authuser", userAccountId), SQL_VarChar("@Id", invoiceId) },
|
||||
Security: dbSec, options: new FIS_SQLOptions());
|
||||
|
||||
if (itemsTable.Rows.Count > 0)
|
||||
{
|
||||
var dtw = new DatatableWriterAsync(itemsTable, Conn, "[dbo].[fds__invoice_items]");
|
||||
dtw.DoSubmit();
|
||||
if (dtw.SubmitException != null)
|
||||
_logger.LogError(dtw.SubmitException, "PersistInvoiceLineItemsAsync: bulk-copy of invoice items failed for invoice {Id}", invoiceId);
|
||||
}
|
||||
}
|
||||
|
||||
public Document GenerateInvoicePdf(FdsInvoiceData invoice, bool draft)
|
||||
{
|
||||
using var act = FuchsTelemetry.StartActivity("invoice.render");
|
||||
|
||||
@@ -253,7 +253,8 @@ public class StartupSelfTestService : BackgroundService
|
||||
|
||||
using var check = new Spire.Pdf.PdfDocument();
|
||||
check.LoadFromStream(ms);
|
||||
string text = check.Pages[0].ExtractText();
|
||||
var extractor = new Spire.Pdf.Texts.PdfTextExtractor(check.Pages[0]);
|
||||
string text = extractor.ExtractText(new Spire.Pdf.Texts.PdfTextExtractOptions());
|
||||
|
||||
return !text.Contains("Evaluation Warning", StringComparison.OrdinalIgnoreCase)
|
||||
&& !text.Contains("created with Spire.PDF", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
Reference in New Issue
Block a user