Complete DI migration: wire all business services end-to-end
Move the intranet off the static-helper / Active-Record pattern onto
constructor-injected services, removing controller coupling and the
sync-over-async (Task.Run().Wait()) hot spots in the data classes.
Services now registered and consumed via DI:
- IBankingService, IPdfService, IMfrClientFactory (singletons)
- IWidgetService, IReportService, IInvoiceService, IReminderService (scoped)
Key changes:
- FuchsWidgetService: real widget logic (sql_table/indicator/html +
rendering_options) ported from the static class, which is deleted.
- FuchsReportService + FuchsVisualization: report engine decoupled from
IntranetController (takes connStr/dbSec/userAccountId); static
FuchsReports deleted.
- InvoiceService / ReminderService: implement load/register/render/store
(previously NotImplementedException stubs). FdsInvoiceData /
FdsReminderData are now pure data holders — all DB + PDF work moved into
the services, async throughout (no Task.Run().Wait()).
- Controllers inject and call the services; all `new FdsMfrClient()` calls
go through IMfrClientFactory.
- Deleted dead code: static Banking, FuchsWidgets, FuchsReports, and the
unused IDbConnectionFactory.
- InternalsVisibleTo("Fuchs.Tests") for testing internal mapping logic.
Tests: 127 passing (Banking tests moved to the service; added data-holder
tests for FdsInvoiceData/FdsReminderData). Full solution builds clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+19
-14
@@ -1,17 +1,20 @@
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Fuchs.intranet;
|
using Fuchs.Services;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using programmersdigest.MT940Parser;
|
using programmersdigest.MT940Parser;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Fuchs.Tests;
|
namespace Fuchs.Tests;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Banking helper robustness tests.
|
/// Banking service robustness tests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BankingDebitCreditMarkTests
|
public class BankingDebitCreditMarkTests
|
||||||
{
|
{
|
||||||
|
private static readonly BankingService Svc = new(NullLogger<BankingService>.Instance);
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(DebitCreditMark.Credit, "C")]
|
[InlineData(DebitCreditMark.Credit, "C")]
|
||||||
[InlineData(DebitCreditMark.Debit, "D")]
|
[InlineData(DebitCreditMark.Debit, "D")]
|
||||||
@@ -19,18 +22,20 @@ public class BankingDebitCreditMarkTests
|
|||||||
[InlineData(DebitCreditMark.ReverseDebit, "RD")]
|
[InlineData(DebitCreditMark.ReverseDebit, "RD")]
|
||||||
public void DebitCreditMarkAbb_ReturnsExpected(DebitCreditMark mark, string expected)
|
public void DebitCreditMarkAbb_ReturnsExpected(DebitCreditMark mark, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, Banking.DebitCreditMarkAbb(mark));
|
Assert.Equal(expected, Svc.DebitCreditMarkAbb(mark));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DebitCreditMarkAbb_UndefinedValue_ReturnsEmpty()
|
public void DebitCreditMarkAbb_UndefinedValue_ReturnsEmpty()
|
||||||
{
|
{
|
||||||
Assert.Equal("", Banking.DebitCreditMarkAbb((DebitCreditMark)999));
|
Assert.Equal("", Svc.DebitCreditMarkAbb((DebitCreditMark)999));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BankingParseToDatatableTests
|
public class BankingParseToDatatableTests
|
||||||
{
|
{
|
||||||
|
private static readonly BankingService Svc = new(NullLogger<BankingService>.Instance);
|
||||||
|
|
||||||
private static readonly string MinimalMT940 =
|
private static readonly string MinimalMT940 =
|
||||||
"\r\n:20:STARTUMSE\r\n" +
|
"\r\n:20:STARTUMSE\r\n" +
|
||||||
":25:DE12345678901234567890\r\n" +
|
":25:DE12345678901234567890\r\n" +
|
||||||
@@ -48,7 +53,7 @@ public class BankingParseToDatatableTests
|
|||||||
public void ParseToDatatable_ValidMT940_ReturnsOneRow()
|
public void ParseToDatatable_ValidMT940_ReturnsOneRow()
|
||||||
{
|
{
|
||||||
using var stream = ToStream(MinimalMT940);
|
using var stream = ToStream(MinimalMT940);
|
||||||
var table = Banking.ParseToDatatable(stream);
|
var table = Svc.ParseToDatatable(stream);
|
||||||
Assert.Equal(1, table.Rows.Count);
|
Assert.Equal(1, table.Rows.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +61,7 @@ public class BankingParseToDatatableTests
|
|||||||
public void ParseToDatatable_ValidMT940_HasAccountColumn()
|
public void ParseToDatatable_ValidMT940_HasAccountColumn()
|
||||||
{
|
{
|
||||||
using var stream = ToStream(MinimalMT940);
|
using var stream = ToStream(MinimalMT940);
|
||||||
var table = Banking.ParseToDatatable(stream);
|
var table = Svc.ParseToDatatable(stream);
|
||||||
Assert.True(table.Columns.Contains("AccountIdentification"));
|
Assert.True(table.Columns.Contains("AccountIdentification"));
|
||||||
Assert.Equal("DE12345678901234567890", table.Rows[0]["AccountIdentification"]);
|
Assert.Equal("DE12345678901234567890", table.Rows[0]["AccountIdentification"]);
|
||||||
}
|
}
|
||||||
@@ -65,7 +70,7 @@ public class BankingParseToDatatableTests
|
|||||||
public void ParseToDatatable_ValidMT940_HasAmountColumn()
|
public void ParseToDatatable_ValidMT940_HasAmountColumn()
|
||||||
{
|
{
|
||||||
using var stream = ToStream(MinimalMT940);
|
using var stream = ToStream(MinimalMT940);
|
||||||
var table = Banking.ParseToDatatable(stream);
|
var table = Svc.ParseToDatatable(stream);
|
||||||
Assert.True(table.Columns.Contains("Amount"));
|
Assert.True(table.Columns.Contains("Amount"));
|
||||||
Assert.Equal(500m, table.Rows[0]["Amount"]);
|
Assert.Equal(500m, table.Rows[0]["Amount"]);
|
||||||
}
|
}
|
||||||
@@ -74,7 +79,7 @@ public class BankingParseToDatatableTests
|
|||||||
public void ParseToDatatable_ValidMT940_HasDebitCreditMark()
|
public void ParseToDatatable_ValidMT940_HasDebitCreditMark()
|
||||||
{
|
{
|
||||||
using var stream = ToStream(MinimalMT940);
|
using var stream = ToStream(MinimalMT940);
|
||||||
var table = Banking.ParseToDatatable(stream);
|
var table = Svc.ParseToDatatable(stream);
|
||||||
Assert.Equal("C", table.Rows[0]["DebitCreditMark"]);
|
Assert.Equal("C", table.Rows[0]["DebitCreditMark"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +87,7 @@ public class BankingParseToDatatableTests
|
|||||||
public void ParseToDatatable_EmptyStream_ReturnsEmptyTable()
|
public void ParseToDatatable_EmptyStream_ReturnsEmptyTable()
|
||||||
{
|
{
|
||||||
using var stream = ToStream("");
|
using var stream = ToStream("");
|
||||||
var table = Banking.ParseToDatatable(stream);
|
var table = Svc.ParseToDatatable(stream);
|
||||||
Assert.Equal(0, table.Rows.Count);
|
Assert.Equal(0, table.Rows.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +95,7 @@ public class BankingParseToDatatableTests
|
|||||||
public void ParseToDatatable_EmptyStream_HasDefaultSchema()
|
public void ParseToDatatable_EmptyStream_HasDefaultSchema()
|
||||||
{
|
{
|
||||||
using var stream = ToStream("");
|
using var stream = ToStream("");
|
||||||
var table = Banking.ParseToDatatable(stream);
|
var table = Svc.ParseToDatatable(stream);
|
||||||
Assert.True(table.Columns.Contains("AccountIdentification"));
|
Assert.True(table.Columns.Contains("AccountIdentification"));
|
||||||
Assert.True(table.Columns.Contains("Amount"));
|
Assert.True(table.Columns.Contains("Amount"));
|
||||||
Assert.True(table.Columns.Contains("DebitCreditMark"));
|
Assert.True(table.Columns.Contains("DebitCreditMark"));
|
||||||
@@ -104,7 +109,7 @@ public class BankingParseToDatatableTests
|
|||||||
schema.Columns.Add("Amount", typeof(decimal));
|
schema.Columns.Add("Amount", typeof(decimal));
|
||||||
|
|
||||||
using var stream = ToStream(MinimalMT940);
|
using var stream = ToStream(MinimalMT940);
|
||||||
var table = Banking.ParseToDatatable(stream, schemaDatatable: schema);
|
var table = Svc.ParseToDatatable(stream, schemaDatatable: schema);
|
||||||
Assert.Equal(2, table.Columns.Count);
|
Assert.Equal(2, table.Columns.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +118,7 @@ public class BankingParseToDatatableTests
|
|||||||
{
|
{
|
||||||
var multi = MinimalMT940 + "\n" + MinimalMT940;
|
var multi = MinimalMT940 + "\n" + MinimalMT940;
|
||||||
using var stream = ToStream(multi);
|
using var stream = ToStream(multi);
|
||||||
var table = Banking.ParseToDatatable(stream);
|
var table = Svc.ParseToDatatable(stream);
|
||||||
Assert.Equal(2, table.Rows.Count);
|
Assert.Equal(2, table.Rows.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +126,7 @@ public class BankingParseToDatatableTests
|
|||||||
public void ParseToDatatable_MalformedContent_DoesNotThrow()
|
public void ParseToDatatable_MalformedContent_DoesNotThrow()
|
||||||
{
|
{
|
||||||
using var stream = ToStream("This is not MT940 data at all");
|
using var stream = ToStream("This is not MT940 data at all");
|
||||||
var ex = Record.Exception(() => Banking.ParseToDatatable(stream));
|
var ex = Record.Exception(() => Svc.ParseToDatatable(stream));
|
||||||
Assert.Null(ex);
|
Assert.Null(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using Fuchs.intranet;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Fuchs.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for the refactored data holders (FdsInvoiceData / FdsReminderData).
|
||||||
|
/// These are now pure data objects — persistence/PDF moved to the DI services.
|
||||||
|
/// </summary>
|
||||||
|
public class FdsDataTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void FdsInvoiceData_Empty_DefaultsToDraftWithNoRegistration()
|
||||||
|
{
|
||||||
|
var inv = new FdsInvoiceData();
|
||||||
|
Assert.True(inv.IsDraft);
|
||||||
|
Assert.Null(inv.InvoiceRegistration);
|
||||||
|
Assert.Equal("", inv.Id);
|
||||||
|
Assert.Equal("R", inv.InvoiceType); // fallback when no registration
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FdsInvoiceData_ParsesFormSections()
|
||||||
|
{
|
||||||
|
var jo = JObject.Parse(
|
||||||
|
"{ \"admin\": { \"type\": \"R\", \"customerid\": \"42\" }, " +
|
||||||
|
" \"new\": { \"title\": \"Test\", \"total_gross\": \"119.00\" }, " +
|
||||||
|
" \"sms\": { \"tscn\": \"10\" }, " +
|
||||||
|
" \"req\": [] }");
|
||||||
|
var inv = new FdsInvoiceData(jo);
|
||||||
|
|
||||||
|
Assert.NotNull(inv.Admin);
|
||||||
|
Assert.NotNull(inv.NewValues);
|
||||||
|
Assert.NotNull(inv.Sms);
|
||||||
|
Assert.Equal("R", inv.Admin!.getString("type"));
|
||||||
|
Assert.Equal("Test", inv.NewValues!.getString("title"));
|
||||||
|
Assert.True(inv.IsDraft);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FdsInvoiceData_BuildInvoiceParams_PicksHighestVatAndMapsFields()
|
||||||
|
{
|
||||||
|
// two line items with different VAT rates → highest (19) selected
|
||||||
|
var jo = JObject.Parse(
|
||||||
|
"{ \"admin\": { \"type\": \"R\", \"customerid\": \"7\", \"p13b\": false }, " +
|
||||||
|
" \"new\": { \"title\": \"Bad\", \"total_gross\": \"119\", \"total_net\": \"100\", \"vat_19_net\": \"100\", \"paymentterm\": \"10d\" }, " +
|
||||||
|
" \"req\": [ { \"items\": [ { \"vat\": \"7%\" }, { \"vat\": \"19%\" } ] } ] }");
|
||||||
|
var inv = new FdsInvoiceData(jo);
|
||||||
|
|
||||||
|
var pl = inv.BuildInvoiceParams(change: false, invId: "");
|
||||||
|
var vat = pl.Find(p => p.ParameterName == "@InvoiceVAT_1");
|
||||||
|
var title = pl.Find(p => p.ParameterName == "@InvoiceTitle");
|
||||||
|
|
||||||
|
Assert.NotNull(vat);
|
||||||
|
Assert.Equal("19", vat!.Value);
|
||||||
|
Assert.Equal("Bad", title!.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FdsReminderData_Empty_DefaultsToDraft()
|
||||||
|
{
|
||||||
|
var rem = new FdsReminderData();
|
||||||
|
Assert.True(rem.IsDraft);
|
||||||
|
Assert.Null(rem.ReminderRegistration);
|
||||||
|
Assert.Equal("", rem.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FdsReminderData_ParsesFormSections()
|
||||||
|
{
|
||||||
|
var jo = JObject.Parse(
|
||||||
|
"{ \"new\": { \"amount\": \"50\", \"invoiceemail\": \"a@b.de\" }, " +
|
||||||
|
" \"rem\": { \"type\": \"f\", \"invid\": \"123\" } }");
|
||||||
|
var rem = new FdsReminderData(jo);
|
||||||
|
|
||||||
|
Assert.NotNull(rem.NewValues);
|
||||||
|
Assert.NotNull(rem.Rem);
|
||||||
|
Assert.Equal("50", rem.NewValues!.getString("amount"));
|
||||||
|
Assert.Equal("f", rem.Rem!.getString("type"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ public partial class IntranetController
|
|||||||
_intranet.Intranet__SQLConnectionString,
|
_intranet.Intranet__SQLConnectionString,
|
||||||
Security: DbSec, options: SqlOpt(fn, id, code))).DataTable;
|
Security: DbSec, options: SqlOpt(fn, id, code))).DataTable;
|
||||||
|
|
||||||
var tbl = Banking.ParseToDatatable(stream, schemaDt);
|
var tbl = _banking.ParseToDatatable(stream, schemaDt);
|
||||||
var tmptbl = "bs_" + Guid.NewGuid().ToString().Replace("-", "");
|
var tmptbl = "bs_" + Guid.NewGuid().ToString().Replace("-", "");
|
||||||
|
|
||||||
var dtwa = new DatatableWriterAsync(tbl, _intranet.Intranet__SQLConnectionString)
|
var dtwa = new DatatableWriterAsync(tbl, _intranet.Intranet__SQLConnectionString)
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ public partial class IntranetController
|
|||||||
return BadRequest400();
|
return BadRequest400();
|
||||||
}
|
}
|
||||||
_logger.LogInformation("mfrrel: resetting MFR relation for invoice {InvoiceId}, user={User}", relId, UserAccountID);
|
_logger.LogInformation("mfrrel: resetting MFR relation for invoice {InvoiceId}, user={User}", relId, UserAccountID);
|
||||||
using (var mfr = new fds.FdsMfrClient())
|
using (var mfr = _mfrFactory.Create())
|
||||||
await mfr.Update__entitytable(EntityTypes.Invoice,
|
await mfr.Update__entitytable(EntityTypes.Invoice,
|
||||||
fds.FdsMfr.UpdateNeed.Reset, new[] { relId });
|
fds.FdsMfr.UpdateNeed.Reset, new[] { relId });
|
||||||
return Ok();
|
return Ok();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public partial class IntranetController
|
|||||||
if (!long.TryParse(Form("id"), out long tgtid)) { _logger.LogWarning("HandleInvoicePget: invalid 'id' value='{Value}' user={User}", Form("id"), UserAccountID); return BadRequest400(); }
|
if (!long.TryParse(Form("id"), out long tgtid)) { _logger.LogWarning("HandleInvoicePget: invalid 'id' value='{Value}' user={User}", Form("id"), UserAccountID); return BadRequest400(); }
|
||||||
_logger.LogDebug("HandleInvoicePget tgtid={TgtId} user={User}", tgtid, UserAccountID);
|
_logger.LogDebug("HandleInvoicePget tgtid={TgtId} user={User}", tgtid, UserAccountID);
|
||||||
|
|
||||||
using (var mfr = new fds.FdsMfrClient())
|
using (var mfr = _mfrFactory.Create())
|
||||||
{
|
{
|
||||||
_logger.LogDebug("HandleInvoicePget resetting invoice entity tgtid={TgtId}", tgtid);
|
_logger.LogDebug("HandleInvoicePget resetting invoice entity tgtid={TgtId}", tgtid);
|
||||||
await mfr.Update__entitytable(EntityTypes.Invoice,
|
await mfr.Update__entitytable(EntityTypes.Invoice,
|
||||||
@@ -50,7 +50,7 @@ public partial class IntranetController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_logger.LogDebug("HandleInvoicePget resetting {InvCount} invoices and {SrqCount} service requests", invIds.Count, srqIds.Count);
|
_logger.LogDebug("HandleInvoicePget resetting {InvCount} invoices and {SrqCount} service requests", invIds.Count, srqIds.Count);
|
||||||
using var mfr2 = new fds.FdsMfrClient();
|
using var mfr2 = _mfrFactory.Create();
|
||||||
foreach (var iid in invIds)
|
foreach (var iid in invIds)
|
||||||
await mfr2.Update__entitytable(EntityTypes.Invoice, fds.FdsMfr.UpdateNeed.Reset, new[] { iid });
|
await mfr2.Update__entitytable(EntityTypes.Invoice, fds.FdsMfr.UpdateNeed.Reset, new[] { iid });
|
||||||
foreach (var iid in srqIds)
|
foreach (var iid in srqIds)
|
||||||
@@ -413,9 +413,9 @@ public partial class IntranetController
|
|||||||
return ldic;
|
return ldic;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<string[]> BuildPdfImageArray(byte[] content)
|
private async Task<string[]> BuildPdfImageArray(byte[] content)
|
||||||
{
|
{
|
||||||
var imgcol = await FuchsPdf.BytesToImageCollection(content);
|
var imgcol = await _pdf.BytesToImageCollectionAsync(content);
|
||||||
return imgcol.ImgB64Array;
|
return imgcol.ImgB64Array;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ public partial class IntranetController
|
|||||||
{
|
{
|
||||||
if (!HasForm("remc")) return BadRequest400();
|
if (!HasForm("remc")) return BadRequest400();
|
||||||
var ctd = JsonConvert.DeserializeObject(Form("remc"))!;
|
var ctd = JsonConvert.DeserializeObject(Form("remc"))!;
|
||||||
var fdRem = new FdsReminderData(ctd);
|
var fdRem = await _reminders.RegisterReminderAsync(
|
||||||
fdRem.RegisterReminder(this, change: false, remId: "");
|
new FdsReminderData(ctd), change: false, remId: "", UserAccountID, DbSec);
|
||||||
if (!string.IsNullOrEmpty(fdRem.Id))
|
if (!string.IsNullOrEmpty(fdRem.Id))
|
||||||
{
|
{
|
||||||
var imgcol = await FuchsPdf.DocToImageCollection(fdRem.ReminderPDF(this));
|
var imgcol = await _pdf.DocToImageCollectionAsync(_reminders.GenerateReminderPdf(fdRem, fdRem.IsDraft));
|
||||||
return await JSONAsync(new { id = fdRem.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
return await JSONAsync(new { id = fdRem.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
||||||
}
|
}
|
||||||
return StatusCode(500, new { error = "Erinnerung wurde nicht registriert" });
|
return StatusCode(500, new { error = "Erinnerung wurde nicht registriert" });
|
||||||
@@ -64,12 +64,11 @@ public partial class IntranetController
|
|||||||
case "rdoc":
|
case "rdoc":
|
||||||
{
|
{
|
||||||
if (!HasForm("id")) return BadRequest400();
|
if (!HasForm("id")) return BadRequest400();
|
||||||
byte[]? fc = null;
|
var (file, fc) = await _reminders.GetStoredFileAsync(Form("id"), UserAccountID, DbSec);
|
||||||
var file = FdsReminderData.GetStoredFile(ref fc, Form("id"), this);
|
if (file == null || fc == null) return StatusCode(404, new { error = "Dokument wurde nicht gefunden" });
|
||||||
if (file == null) return StatusCode(404, new { error = "Dokument wurde nicht gefunden" });
|
|
||||||
return Form("typ") != "img"
|
return Form("typ") != "img"
|
||||||
? await FileContentResultAsync(fc!, file.MimeType(), file.Name)
|
? await FileContentResultAsync(fc, file.MimeType(), file.Name)
|
||||||
: await JSONAsync(new { id = Form("id"), img = await BuildPdfImageArray(fc!) });
|
: await JSONAsync(new { id = Form("id"), img = await BuildPdfImageArray(fc) });
|
||||||
}
|
}
|
||||||
|
|
||||||
case "idoc": return await HandleReminderIdoc(fn, id, code);
|
case "idoc": return await HandleReminderIdoc(fn, id, code);
|
||||||
@@ -107,8 +106,8 @@ public partial class IntranetController
|
|||||||
if (frdic.TryGetValue("IsFinal", out var isFinal) && isFinal is true)
|
if (frdic.TryGetValue("IsFinal", out var isFinal) && isFinal is true)
|
||||||
{
|
{
|
||||||
string remId = frdic["Id"]?.ToString() ?? "";
|
string remId = frdic["Id"]?.ToString() ?? "";
|
||||||
var fdRem = new FdsReminderData(remId, this);
|
var fdRem = await _reminders.LoadReminderAsync(remId, UserAccountID, DbSec);
|
||||||
byte[] filebyte = await fdRem.StoreReminderDocumentFile(this);
|
byte[] filebyte = await _reminders.StoreReminderDocumentFileAsync(fdRem, fdRem.IsDraft, UserAccountID, DbSec);
|
||||||
string email = frdic.nz("SendToEmail", "");
|
string email = frdic.nz("SendToEmail", "");
|
||||||
if (!string.IsNullOrEmpty(email) && filebyte.Length > 0)
|
if (!string.IsNullOrEmpty(email) && filebyte.Length > 0)
|
||||||
{
|
{
|
||||||
@@ -140,17 +139,17 @@ public partial class IntranetController
|
|||||||
private async Task<IActionResult> HandleReminderIdoc(string fn, string id, string code)
|
private async Task<IActionResult> HandleReminderIdoc(string fn, string id, string code)
|
||||||
{
|
{
|
||||||
if (!HasForm("id") || string.IsNullOrEmpty(Form("id"))) return StatusCode(404);
|
if (!HasForm("id") || string.IsNullOrEmpty(Form("id"))) return StatusCode(404);
|
||||||
var fdRem = new FdsReminderData(Form("id"), this);
|
var fdRem = await _reminders.LoadReminderAsync(Form("id"), UserAccountID, DbSec);
|
||||||
if (string.IsNullOrEmpty(fdRem.Id)) return StatusCode(404, new { error = "Erinnerung wurde nicht gefunden" });
|
if (string.IsNullOrEmpty(fdRem.Id)) return StatusCode(404, new { error = "Erinnerung wurde nicht gefunden" });
|
||||||
string filename = fdRem.ReminderRegistration.nz("DocumentName").ne($"Zahlungserinnerung_{fdRem.Id}.pdf");
|
string filename = fdRem.ReminderRegistration!.nz("DocumentName").ne($"Zahlungserinnerung_{fdRem.Id}.pdf");
|
||||||
if (Form("typ") != "img")
|
if (Form("typ") != "img")
|
||||||
{
|
{
|
||||||
byte[] ct = Form("create", "0") != "1"
|
byte[] ct = Form("create", "0") != "1"
|
||||||
? (await fdRem.GetReminderFile(this)) is { Length: > 0 } f1 ? f1 : await fdRem.StoreReminderDocumentFile(this)
|
? (await _reminders.GetReminderFileAsync(fdRem, fdRem.IsDraft, _mfr, UserAccountID, DbSec)) is { Length: > 0 } f1 ? f1 : await _reminders.StoreReminderDocumentFileAsync(fdRem, fdRem.IsDraft, UserAccountID, DbSec)
|
||||||
: FuchsPdf.DocToPdfBytes(fdRem.ReminderPDF(this));
|
: _pdf.DocToPdfBytes(_reminders.GenerateReminderPdf(fdRem, fdRem.IsDraft));
|
||||||
return await FileContentResultAsync(ct, "application/pdf", filename, inline: true);
|
return await FileContentResultAsync(ct, "application/pdf", filename, inline: true);
|
||||||
}
|
}
|
||||||
var imgcol = await FuchsPdf.DocToImageCollection(fdRem.ReminderPDF(this));
|
var imgcol = await _pdf.DocToImageCollectionAsync(_reminders.GenerateReminderPdf(fdRem, fdRem.IsDraft));
|
||||||
return await JSONAsync(new { id = fdRem.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
return await JSONAsync(new { id = fdRem.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public partial class IntranetController
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return await FuchsReports.ProcessFdsRequest(this, id.ToLower(), code);
|
return await _reports.ProcessRequestAsync(id.ToLower(), code, UserAccountID, DbSec, RequestParamsDict());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ public partial class IntranetController
|
|||||||
case "save":
|
case "save":
|
||||||
{
|
{
|
||||||
if (!HasForm("invc")) return BadRequest400();
|
if (!HasForm("invc")) return BadRequest400();
|
||||||
var fdInv = new FdsInvoiceData(JsonConvert.DeserializeObject(Form("invc"))!);
|
var fdInv = await _invoices.RegisterInvoiceAsync(
|
||||||
fdInv.RegisterInvoice(this, change: !string.IsNullOrEmpty(Form("id")), invId: Form("id"));
|
new FdsInvoiceData(JsonConvert.DeserializeObject(Form("invc"))!),
|
||||||
|
change: !string.IsNullOrEmpty(Form("id")), invId: Form("id"), UserAccountID, DbSec);
|
||||||
return !string.IsNullOrEmpty(fdInv.Id)
|
return !string.IsNullOrEmpty(fdInv.Id)
|
||||||
? await JSONAsync(new { id = fdInv.Id })
|
? await JSONAsync(new { id = fdInv.Id })
|
||||||
: StatusCode(500, new { error = "Rechnung wurde nicht gespeichert" });
|
: StatusCode(500, new { error = "Rechnung wurde nicht gespeichert" });
|
||||||
@@ -55,11 +56,12 @@ public partial class IntranetController
|
|||||||
case "sprep":
|
case "sprep":
|
||||||
{
|
{
|
||||||
if (!HasForm("invc")) return BadRequest400();
|
if (!HasForm("invc")) return BadRequest400();
|
||||||
var fdInv = new FdsInvoiceData(JsonConvert.DeserializeObject(Form("invc"))!);
|
var fdInv = await _invoices.RegisterInvoiceAsync(
|
||||||
fdInv.RegisterInvoice(this, change: false, invId: "");
|
new FdsInvoiceData(JsonConvert.DeserializeObject(Form("invc"))!),
|
||||||
|
change: false, invId: "", UserAccountID, DbSec);
|
||||||
if (!string.IsNullOrEmpty(fdInv.Id))
|
if (!string.IsNullOrEmpty(fdInv.Id))
|
||||||
{
|
{
|
||||||
var imgcol = await FuchsPdf.DocToImageCollection(fdInv.InvoicePDF(this));
|
var imgcol = await _pdf.DocToImageCollectionAsync(_invoices.GenerateInvoicePdf(fdInv, fdInv.IsDraft));
|
||||||
return await JSONAsync(new { id = fdInv.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
return await JSONAsync(new { id = fdInv.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
||||||
}
|
}
|
||||||
return StatusCode(500, new { error = "Rechnung wurde nicht registriert" });
|
return StatusCode(500, new { error = "Rechnung wurde nicht registriert" });
|
||||||
@@ -68,11 +70,12 @@ public partial class IntranetController
|
|||||||
case "sedit":
|
case "sedit":
|
||||||
{
|
{
|
||||||
if (!HasForm("id", "invc")) return BadRequest400();
|
if (!HasForm("id", "invc")) return BadRequest400();
|
||||||
var fdInv = new FdsInvoiceData(JsonConvert.DeserializeObject(Form("invc"))!);
|
var fdInv = await _invoices.RegisterInvoiceAsync(
|
||||||
fdInv.RegisterInvoice(this, change: true, invId: Form("id"));
|
new FdsInvoiceData(JsonConvert.DeserializeObject(Form("invc"))!),
|
||||||
|
change: true, invId: Form("id"), UserAccountID, DbSec);
|
||||||
if (!string.IsNullOrEmpty(fdInv.Id))
|
if (!string.IsNullOrEmpty(fdInv.Id))
|
||||||
{
|
{
|
||||||
var imgcol = await FuchsPdf.DocToImageCollection(fdInv.InvoicePDF(this));
|
var imgcol = await _pdf.DocToImageCollectionAsync(_invoices.GenerateInvoicePdf(fdInv, fdInv.IsDraft));
|
||||||
return await JSONAsync(new { id = fdInv.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
return await JSONAsync(new { id = fdInv.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
||||||
}
|
}
|
||||||
return StatusCode(500, new { error = "Rechnung wurde nicht registriert" });
|
return StatusCode(500, new { error = "Rechnung wurde nicht registriert" });
|
||||||
@@ -171,7 +174,7 @@ public partial class IntranetController
|
|||||||
[EntityHelper.EntityName(EntityTypes.ServiceRequest)] =
|
[EntityHelper.EntityName(EntityTypes.ServiceRequest)] =
|
||||||
new fds.FdsMfrClient.DatabaseSchema(EntityTypes.ServiceRequest)
|
new fds.FdsMfrClient.DatabaseSchema(EntityTypes.ServiceRequest)
|
||||||
};
|
};
|
||||||
using var mfr = new fds.FdsMfrClient();
|
using var mfr = _mfrFactory.Create();
|
||||||
await mfr.Update__entitytable(EntityTypes.ServiceRequest,
|
await mfr.Update__entitytable(EntityTypes.ServiceRequest,
|
||||||
fds.FdsMfr.UpdateNeed.Reset, ids.ToArray(), schemaDic: schemaDic);
|
fds.FdsMfr.UpdateNeed.Reset, ids.ToArray(), schemaDic: schemaDic);
|
||||||
return Ok();
|
return Ok();
|
||||||
@@ -258,8 +261,8 @@ public partial class IntranetController
|
|||||||
if (frdic.TryGetValue("IsFinal", out var isFinal) && isFinal is true)
|
if (frdic.TryGetValue("IsFinal", out var isFinal) && isFinal is true)
|
||||||
{
|
{
|
||||||
string invId = frdic["Id"]?.ToString() ?? "";
|
string invId = frdic["Id"]?.ToString() ?? "";
|
||||||
var fdInv = new FdsInvoiceData(invId, this);
|
var fdInv = await _invoices.LoadInvoiceAsync(invId, UserAccountID, DbSec);
|
||||||
byte[] filebyte = await fdInv.StoreInvoiceDocumentFile(this);
|
byte[] filebyte = await _invoices.StoreInvoiceDocumentFileAsync(fdInv, fdInv.IsDraft, UserAccountID, DbSec);
|
||||||
var dtset = await getSQLDataSet_async(
|
var dtset = await getSQLDataSet_async(
|
||||||
"EXECUTE [dbo].[fds__getInvoice] @Id, @authuser;",
|
"EXECUTE [dbo].[fds__getInvoice] @Id, @authuser;",
|
||||||
_intranet.Intranet__SQLConnectionString,
|
_intranet.Intranet__SQLConnectionString,
|
||||||
@@ -293,19 +296,19 @@ public partial class IntranetController
|
|||||||
private async Task<IActionResult> HandleRequestIdoc(string fn, string id, string code)
|
private async Task<IActionResult> HandleRequestIdoc(string fn, string id, string code)
|
||||||
{
|
{
|
||||||
if (!HasForm("id") || string.IsNullOrEmpty(Form("id"))) return StatusCode(404);
|
if (!HasForm("id") || string.IsNullOrEmpty(Form("id"))) return StatusCode(404);
|
||||||
var fdInv = new FdsInvoiceData(Form("id"), this);
|
var fdInv = await _invoices.LoadInvoiceAsync(Form("id"), UserAccountID, DbSec);
|
||||||
if (string.IsNullOrEmpty(fdInv.Id)) return StatusCode(404, new { error = "Rechnung wurde nicht gefunden" });
|
if (string.IsNullOrEmpty(fdInv.Id)) return StatusCode(404, new { error = "Rechnung wurde nicht gefunden" });
|
||||||
string filename = fdInv.InvoiceRegistration.nz("DocumentName").ne($"Rechnung_{fdInv.Id}.pdf");
|
string filename = fdInv.InvoiceRegistration!.nz("DocumentName").ne($"Rechnung_{fdInv.Id}.pdf");
|
||||||
if (Form("typ") != "img")
|
if (Form("typ") != "img")
|
||||||
{
|
{
|
||||||
byte[]? ct = Form("create", "0") != "1"
|
byte[]? ct = Form("create", "0") != "1"
|
||||||
? await fdInv.GetInvoiceFile(this) is { Length: > 0 } f1 ? f1 : await fdInv.StoreInvoiceDocumentFile(this)
|
? await _invoices.GetInvoiceFileAsync(fdInv, fdInv.IsDraft, _mfr) is { Length: > 0 } f1 ? f1 : await _invoices.StoreInvoiceDocumentFileAsync(fdInv, fdInv.IsDraft, UserAccountID, DbSec)
|
||||||
: FuchsPdf.DocToPdfBytes(fdInv.InvoicePDF(this));
|
: _pdf.DocToPdfBytes(_invoices.GenerateInvoicePdf(fdInv, fdInv.IsDraft));
|
||||||
return ct != null
|
return ct != null
|
||||||
? await FileContentResultAsync(ct, "application/pdf", filename, inline: true)
|
? await FileContentResultAsync(ct, "application/pdf", filename, inline: true)
|
||||||
: StatusCode(500, new { error = "Rechnungs-PDF konnte nicht erstellt werden" });
|
: StatusCode(500, new { error = "Rechnungs-PDF konnte nicht erstellt werden" });
|
||||||
}
|
}
|
||||||
var imgcol = await FuchsPdf.DocToImageCollection(fdInv.InvoicePDF(this));
|
var imgcol = await _pdf.DocToImageCollectionAsync(_invoices.GenerateInvoicePdf(fdInv, fdInv.IsDraft));
|
||||||
return await JSONAsync(new { id = fdInv.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
return await JSONAsync(new { id = fdInv.Id, img = imgcol.ImgB64Array, total = imgcol.TotalPages });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,8 +325,8 @@ public partial class IntranetController
|
|||||||
if (frdic.TryGetValue("IsFinal", out var isFinal) && isFinal is true)
|
if (frdic.TryGetValue("IsFinal", out var isFinal) && isFinal is true)
|
||||||
{
|
{
|
||||||
string invId = frdic["Id"]?.ToString() ?? "";
|
string invId = frdic["Id"]?.ToString() ?? "";
|
||||||
var fdInv = new FdsInvoiceData(invId, this);
|
var fdInv = await _invoices.LoadInvoiceAsync(invId, UserAccountID, DbSec);
|
||||||
byte[] filebyte = FuchsPdf.DocToPdfBytes(fdInv.InvoicePDF(this));
|
byte[] filebyte = await _invoices.RenderInvoicePdfBytesAsync(fdInv, fdInv.IsDraft);
|
||||||
string email = frdic.nz("SendToEmail", "");
|
string email = frdic.nz("SendToEmail", "");
|
||||||
if (!string.IsNullOrEmpty(email) && filebyte.Length > 0)
|
if (!string.IsNullOrEmpty(email) && filebyte.Length > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
internal readonly fds.IFdsMfr _mfr;
|
internal readonly fds.IFdsMfr _mfr;
|
||||||
private readonly ILogger<IntranetController> _logger;
|
private readonly ILogger<IntranetController> _logger;
|
||||||
private readonly IComService _comService;
|
private readonly IComService _comService;
|
||||||
|
private readonly IBankingService _banking;
|
||||||
|
private readonly IPdfService _pdf;
|
||||||
|
private readonly IMfrClientFactory _mfrFactory;
|
||||||
|
private readonly IWidgetService _widgets;
|
||||||
|
private readonly IReportService _reports;
|
||||||
|
private readonly IInvoiceService _invoices;
|
||||||
|
private readonly IReminderService _reminders;
|
||||||
private readonly List<string> _allowedNonAuth = new() { "spwc", "spw" };
|
private readonly List<string> _allowedNonAuth = new() { "spwc", "spw" };
|
||||||
private readonly List<string> _allowedGet = new()
|
private readonly List<string> _allowedGet = new()
|
||||||
{
|
{
|
||||||
@@ -41,12 +48,40 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
public string UserAccountID => UserIdent.UserAccountId;
|
public string UserAccountID => UserIdent.UserAccountId;
|
||||||
public string AuthAccount => UserIdent.Email;
|
public string AuthAccount => UserIdent.Email;
|
||||||
|
|
||||||
public IntranetController(Fuchs_intranet intranet, fds.IFdsMfr mfr, ILogger<IntranetController> logger, IComService comService)
|
public IntranetController(
|
||||||
|
Fuchs_intranet intranet,
|
||||||
|
fds.IFdsMfr mfr,
|
||||||
|
ILogger<IntranetController> logger,
|
||||||
|
IComService comService,
|
||||||
|
IBankingService banking,
|
||||||
|
IPdfService pdf,
|
||||||
|
IMfrClientFactory mfrFactory,
|
||||||
|
IWidgetService widgets,
|
||||||
|
IReportService reports,
|
||||||
|
IInvoiceService invoices,
|
||||||
|
IReminderService reminders)
|
||||||
{
|
{
|
||||||
_intranet = intranet;
|
_intranet = intranet;
|
||||||
_mfr = mfr;
|
_mfr = mfr;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_comService = comService;
|
_comService = comService;
|
||||||
|
_banking = banking;
|
||||||
|
_pdf = pdf;
|
||||||
|
_mfrFactory = mfrFactory;
|
||||||
|
_widgets = widgets;
|
||||||
|
_reports = reports;
|
||||||
|
_invoices = invoices;
|
||||||
|
_reminders = reminders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Merged query-string + form parameters (form wins) for report processing.</summary>
|
||||||
|
internal Dictionary<string, string> RequestParamsDict()
|
||||||
|
{
|
||||||
|
var prms = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var kv in Request.Query) prms[kv.Key] = kv.Value.ToString();
|
||||||
|
if (Request.HasFormContentType)
|
||||||
|
foreach (var kv in Request.Form) prms[kv.Key] = kv.Value.ToString();
|
||||||
|
return prms;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Standard param list (pre-populates @authuser) ────────────────────────
|
// ── Standard param list (pre-populates @authuser) ────────────────────────
|
||||||
@@ -106,7 +141,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
IActionResult? result = fn.ToLower() switch
|
IActionResult? result = fn.ToLower() switch
|
||||||
{
|
{
|
||||||
"ping" => Ok(),
|
"ping" => Ok(),
|
||||||
"wdg" => await FuchsWidgets.IntranetWdg(this, id),
|
"wdg" => await _widgets.GetWidgetAsync(id, UserAccountID, DbSec, Request),
|
||||||
"todos" => new PhysicalFileResult(
|
"todos" => new PhysicalFileResult(
|
||||||
Path.Combine(Directory.GetCurrentDirectory(), "Data", "ProjectToDos.html"),
|
Path.Combine(Directory.GetCurrentDirectory(), "Data", "ProjectToDos.html"),
|
||||||
"text/html"),
|
"text/html"),
|
||||||
@@ -374,7 +409,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
if (string.IsNullOrEmpty(id))
|
if (string.IsNullOrEmpty(id))
|
||||||
{
|
{
|
||||||
// Empty id → return the OData EDMX schema ($metadata), matching legacy fds.getSchema()
|
// Empty id → return the OData EDMX schema ($metadata), matching legacy fds.getSchema()
|
||||||
using var mfrSchema = new fds.FdsMfrClient();
|
using var mfrSchema = _mfrFactory.Create();
|
||||||
string schema = await mfrSchema.ReadAnything(
|
string schema = await mfrSchema.ReadAnything(
|
||||||
mfrSchema.ClientConfig.BaseUrl + "$metadata", throwErrorIfNotOk: false);
|
mfrSchema.ClientConfig.BaseUrl + "$metadata", throwErrorIfNotOk: false);
|
||||||
return Content(schema, "text/xml", System.Text.Encoding.UTF8);
|
return Content(schema, "text/xml", System.Text.Encoding.UTF8);
|
||||||
@@ -383,7 +418,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
{
|
{
|
||||||
string path = id + (!string.IsNullOrEmpty(code) ? "/" + code : HttpUtility.UrlDecode(Request.QueryString.Value ?? ""));
|
string path = id + (!string.IsNullOrEmpty(code) ? "/" + code : HttpUtility.UrlDecode(Request.QueryString.Value ?? ""));
|
||||||
_logger.LogDebug("HandleMfr reading OData path={Path} user={User}", path, UserAccountID);
|
_logger.LogDebug("HandleMfr reading OData path={Path} user={User}", path, UserAccountID);
|
||||||
using var mfrRead = new fds.FdsMfrClient();
|
using var mfrRead = _mfrFactory.Create();
|
||||||
var result = await mfrRead.ReadOData(path, throwErrorIfNotOk: false);
|
var result = await mfrRead.ReadOData(path, throwErrorIfNotOk: false);
|
||||||
_logger.LogDebug("HandleMfr OData read complete for path={Path} user={User}", path, UserAccountID);
|
_logger.LogDebug("HandleMfr OData read complete for path={Path} user={User}", path, UserAccountID);
|
||||||
return Content(JsonConvert.SerializeObject(result), "application/json");
|
return Content(JsonConvert.SerializeObject(result), "application/json");
|
||||||
@@ -402,7 +437,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
if (et != EntityTypes.none && string.IsNullOrEmpty(Request.Form["need"]))
|
if (et != EntityTypes.none && string.IsNullOrEmpty(Request.Form["need"]))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("MfrUpdate entity={EntityType} need=Short user={User}", et, UserAccountID);
|
_logger.LogInformation("MfrUpdate entity={EntityType} need=Short user={User}", et, UserAccountID);
|
||||||
using var mfrSingle = new fds.FdsMfrClient();
|
using var mfrSingle = _mfrFactory.Create();
|
||||||
await mfrSingle.Update__entitytable(et, fds.FdsMfr.UpdateNeed.Short);
|
await mfrSingle.Update__entitytable(et, fds.FdsMfr.UpdateNeed.Short);
|
||||||
_logger.LogDebug("MfrUpdate Short completed for entity={EntityType}", et);
|
_logger.LogDebug("MfrUpdate Short completed for entity={EntityType}", et);
|
||||||
return Ok();
|
return Ok();
|
||||||
@@ -411,7 +446,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
|||||||
{
|
{
|
||||||
var need = fds.FdsMfr.UpdateNeedValue(needParam);
|
var need = fds.FdsMfr.UpdateNeedValue(needParam);
|
||||||
_logger.LogInformation("MfrUpdate entity={EntityType} need={Need} user={User}", et, need, UserAccountID);
|
_logger.LogInformation("MfrUpdate entity={EntityType} need={Need} user={User}", et, need, UserAccountID);
|
||||||
using var mfr = new fds.FdsMfrClient();
|
using var mfr = _mfrFactory.Create();
|
||||||
await mfr.Update__entitytable(et, updateNeed: need, debugDetails: false);
|
await mfr.Update__entitytable(et, updateNeed: need, debugDetails: false);
|
||||||
_logger.LogDebug("MfrUpdate completed for entity={EntityType} need={Need}", et, need);
|
_logger.LogDebug("MfrUpdate completed for entity={EntityType} need={Need}", et, need);
|
||||||
return Ok();
|
return Ok();
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
<Configurations>db-dev.processweb.de;Debug;Release;server02.processweb.de</Configurations>
|
<Configurations>db-dev.processweb.de;Debug;Release;server02.processweb.de</Configurations>
|
||||||
<NoWarn>CA1416</NoWarn>
|
<NoWarn>CA1416</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Expose internal members (e.g. FdsInvoiceData.BuildInvoiceParams) to the test project -->
|
||||||
|
<InternalsVisibleTo Include="Fuchs.Tests" />
|
||||||
|
</ItemGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -75,6 +75,15 @@ public class Program
|
|||||||
builder.Services.Configure<ProcessWebComSettings>(builder.Configuration.GetSection("Fuchs:Mailer"));
|
builder.Services.Configure<ProcessWebComSettings>(builder.Configuration.GetSection("Fuchs:Mailer"));
|
||||||
builder.Services.AddHttpClient("ProcessWebMailer");
|
builder.Services.AddHttpClient("ProcessWebMailer");
|
||||||
builder.Services.AddScoped<IComService, ProcessWebComService>();
|
builder.Services.AddScoped<IComService, ProcessWebComService>();
|
||||||
|
|
||||||
|
// Business services (DI migration — replaces the static helper / Active-Record pattern)
|
||||||
|
builder.Services.AddSingleton<IBankingService, BankingService>(); // stateless parser
|
||||||
|
builder.Services.AddSingleton<IPdfService, FuchsPdfService>(); // stateless renderer (sets license)
|
||||||
|
builder.Services.AddSingleton<IMfrClientFactory, MfrClientFactory>();
|
||||||
|
builder.Services.AddScoped<IWidgetService, FuchsWidgetService>();
|
||||||
|
builder.Services.AddScoped<IReportService, FuchsReportService>();
|
||||||
|
builder.Services.AddScoped<IInvoiceService, InvoiceService>();
|
||||||
|
builder.Services.AddScoped<IReminderService, ReminderService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ConfigureApp(WebApplication app)
|
private static void ConfigureApp(WebApplication app)
|
||||||
|
|||||||
@@ -1,27 +1,136 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Fuchs.intranet;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using OCORE.security;
|
using OCORE.security;
|
||||||
using OCORE.SQL;
|
using OCORE.SQL;
|
||||||
|
using static OCORE.commons;
|
||||||
|
using static OCORE.OCORE_dictionaries;
|
||||||
|
using static OCORE.SQL.sql;
|
||||||
|
|
||||||
namespace Fuchs.Services;
|
namespace Fuchs.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Report service implementation. Replaces the static <c>FuchsReports</c> class.
|
/// Report processing for the Fuchs intranet — SQL-driven reports from the
|
||||||
|
/// fds__ report catalog, rendered as HTML pages, HTML fragments, or PNG charts
|
||||||
|
/// via <see cref="FuchsVisualization"/>. Replaces the static <c>FuchsReports</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FuchsReportService : IReportService
|
public class FuchsReportService : IReportService
|
||||||
{
|
{
|
||||||
|
private const int DefaultReloadSeconds = 60 * 10;
|
||||||
|
|
||||||
|
private readonly Fuchs_intranet _intranet;
|
||||||
private readonly ILogger<FuchsReportService> _logger;
|
private readonly ILogger<FuchsReportService> _logger;
|
||||||
|
|
||||||
public FuchsReportService(ILogger<FuchsReportService> logger)
|
public FuchsReportService(Fuchs_intranet intranet, ILogger<FuchsReportService> logger)
|
||||||
{
|
{
|
||||||
|
_intranet = intranet;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IActionResult> ProcessRequestAsync(string action, string id,
|
private string Conn => _intranet.Intranet__SQLConnectionString;
|
||||||
string userAccountId, DatabaseSecurity dbSec)
|
|
||||||
|
public async Task<IActionResult> ProcessRequestAsync(string fnc, string reportId,
|
||||||
|
string userAccountId, DatabaseSecurity dbSec, IDictionary<string, string> parameters)
|
||||||
{
|
{
|
||||||
// Specific report actions are dispatched here.
|
var prms = new Dictionary<string, string>(parameters, StringComparer.OrdinalIgnoreCase)
|
||||||
// Extend with additional cases as needed.
|
{
|
||||||
return Task.FromResult<IActionResult>(new OkResult());
|
["@authuser"] = userAccountId
|
||||||
|
};
|
||||||
|
|
||||||
|
string tgt = (string.IsNullOrEmpty(fnc)
|
||||||
|
? (prms.TryGetValue("fnc", out var f) ? f : fnc)
|
||||||
|
: fnc).Replace("gct", "generic_content");
|
||||||
|
string report = prms.TryGetValue("report", out var r) && !string.IsNullOrEmpty(r)
|
||||||
|
? r
|
||||||
|
: (!string.IsNullOrEmpty(reportId) ? reportId : "");
|
||||||
|
|
||||||
|
string templatePath = Path.Combine(AppContext.BaseDirectory, "Content", "FDS_Template.html");
|
||||||
|
|
||||||
|
// Report configuration (refresh interval + cache flag) from the catalog.
|
||||||
|
int ciRefresh = -2;
|
||||||
|
bool ciCache = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var catalog = await getSQLDatatable_async(
|
||||||
|
"EXECUTE [dbo].[fds__admin_getReportCatalog] @report_name, @authuser;",
|
||||||
|
Conn,
|
||||||
|
new List<SqlParameter> { SQL_VarChar("@report_name", report), SQL_VarChar("@authuser", userAccountId) },
|
||||||
|
Security: dbSec, options: new FIS_SQLOptions());
|
||||||
|
var cfg = catalog.FirstRow.toObjectDictionary();
|
||||||
|
if (cfg.TryGetValue("refresh", out var rf) && rf is not null && rf is not DBNull &&
|
||||||
|
int.TryParse(rf.ToString(), out var rfi)) ciRefresh = rfi;
|
||||||
|
if (cfg.TryGetValue("functions", out var fn) && fn is not null)
|
||||||
|
ciCache = (fn.ToString() ?? "").Split(',').Contains("cache");
|
||||||
|
}
|
||||||
|
catch (Exception cex)
|
||||||
|
{
|
||||||
|
_logger.LogError(cex, "Report catalog read failed for report {Report}", report);
|
||||||
|
}
|
||||||
|
bool ciForce = prms.TryGetValue("cache", out var ca) && ca.ToLower() == "0";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (tgt)
|
||||||
|
{
|
||||||
|
case "generic_content":
|
||||||
|
if (string.IsNullOrEmpty(report)) return new StatusCodeResult(300);
|
||||||
|
return new ContentResult
|
||||||
|
{
|
||||||
|
Content = await FuchsVisualization.RenderContentAsync(
|
||||||
|
Conn, dbSec, userAccountId, report, FdsQueryType.generic, prms),
|
||||||
|
ContentType = "text/html"
|
||||||
|
};
|
||||||
|
|
||||||
|
case "generic":
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(report)) return new StatusCodeResult(300);
|
||||||
|
var page = await FuchsVisualization.RenderPageAsync(
|
||||||
|
Conn, dbSec, userAccountId, report, report, FdsQueryType.generic, prms,
|
||||||
|
FdsDestination.web, templatePath, allowcache: ciCache, forceReload: ciForce);
|
||||||
|
ApplyReload(page, prms, ciRefresh);
|
||||||
|
return new ContentResult { Content = page.ToHtml(FdsDestination.web), ContentType = "text/html" };
|
||||||
|
}
|
||||||
|
|
||||||
|
case "chart":
|
||||||
|
byte[]? png = await FuchsVisualization.RenderQueryAsChartAsync(
|
||||||
|
Conn, dbSec, userAccountId, report, FdsQueryType.generic, prms);
|
||||||
|
if (png is null) return new StatusCodeResult(500);
|
||||||
|
return new FileContentResult(png, "image/png")
|
||||||
|
{
|
||||||
|
FileDownloadName = $"{report.Replace(" ", "_")}_{DateTime.Now:yyyyMMdd_HHmm}.png"
|
||||||
|
};
|
||||||
|
|
||||||
|
case "xls":
|
||||||
|
return new StatusCodeResult(501); // not implemented (matches legacy NotImplementedException)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (Enum.TryParse<FdsQueryType>(fnc, ignoreCase: true, out var qt))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(report)) return new StatusCodeResult(300);
|
||||||
|
var page = await FuchsVisualization.RenderPageAsync(
|
||||||
|
Conn, dbSec, userAccountId, report, report, qt, prms,
|
||||||
|
FdsDestination.web, templatePath);
|
||||||
|
ApplyReload(page, prms, -2);
|
||||||
|
return new ContentResult { Content = page.ToHtml(FdsDestination.web), ContentType = "text/html" };
|
||||||
|
}
|
||||||
|
return new StatusCodeResult(300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Report processing failed fnc={Fnc} report={Report} tgt={Tgt}", fnc, report, tgt);
|
||||||
|
return new StatusCodeResult(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyReload(FuchsHtmlPage page, IDictionary<string, string> prms, int ciRefresh)
|
||||||
|
{
|
||||||
|
if (prms.TryGetValue("reload", out var rl) && int.TryParse(rl, out var rs)) page.ReloadSeconds = rs;
|
||||||
|
else if (ciRefresh > -2) page.ReloadSeconds = ciRefresh;
|
||||||
|
else if (DefaultReloadSeconds > 0) page.ReloadSeconds = DefaultReloadSeconds;
|
||||||
|
|
||||||
|
if (page.QueryDuration > 180 && page.ReloadSeconds is > 0 and < 3600) page.ReloadSeconds = 1200;
|
||||||
|
else if (page.QueryDuration > 60 && page.ReloadSeconds is > 0 and < 1200) page.ReloadSeconds = 1200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Fuchs.intranet;
|
using Fuchs.intranet;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using OCORE.security;
|
using OCORE.security;
|
||||||
using OCORE.SQL;
|
using OCORE.SQL;
|
||||||
@@ -12,8 +11,9 @@ using static OCORE.web.mvc_helper_async;
|
|||||||
namespace Fuchs.Services;
|
namespace Fuchs.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Widget service implementation. Replaces the static <c>FuchsWidgets</c> class.
|
/// Widget service for the Fuchs intranet dashboard. Port of fuchs_fds_widgets.vb —
|
||||||
/// No longer depends on <c>IntranetController</c>.
|
/// SQL-driven widget cases. Replaces the static <c>FuchsWidgets</c> class
|
||||||
|
/// (no longer depends on <c>IntranetController</c>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FuchsWidgetService : IWidgetService
|
public class FuchsWidgetService : IWidgetService
|
||||||
{
|
{
|
||||||
@@ -26,6 +26,8 @@ public class FuchsWidgetService : IWidgetService
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string Conn => _intranet.Intranet__SQLConnectionString;
|
||||||
|
|
||||||
public async Task<IActionResult> GetWidgetAsync(string widgetId, string userAccountId,
|
public async Task<IActionResult> GetWidgetAsync(string widgetId, string userAccountId,
|
||||||
DatabaseSecurity dbSec, HttpRequest request)
|
DatabaseSecurity dbSec, HttpRequest request)
|
||||||
{
|
{
|
||||||
@@ -45,20 +47,20 @@ public class FuchsWidgetService : IWidgetService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SqlParameter> MakeParams(string userAccountId, params SqlParameter[] extra)
|
private static List<Microsoft.Data.SqlClient.SqlParameter> Params(string userAccountId,
|
||||||
|
params Microsoft.Data.SqlClient.SqlParameter[] extra)
|
||||||
{
|
{
|
||||||
var list = new List<SqlParameter> { SQL_VarChar("@authuser", userAccountId) };
|
var list = new List<Microsoft.Data.SqlClient.SqlParameter> { SQL_VarChar("@authuser", userAccountId) };
|
||||||
list.AddRange(extra);
|
list.AddRange(extra);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── "my" — list of widget short-names for the current user ───────────────
|
||||||
private async Task<IActionResult> HandleWidgetMy(string userAccountId, DatabaseSecurity dbSec)
|
private async Task<IActionResult> HandleWidgetMy(string userAccountId, DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
var dt = await getSQLDatatable_async(
|
var dt = await getSQLDatatable_async(
|
||||||
"SELECT * FROM [dbo].[fis_getONEPersonWidgets] (@authuser);",
|
"SELECT * FROM [dbo].[fis_getONEPersonWidgets] (@authuser);",
|
||||||
_intranet.Intranet__SQLConnectionString,
|
Conn, Params(userAccountId, SQL_VarChar("@account", "fis")), Security: dbSec);
|
||||||
MakeParams(userAccountId, SQL_VarChar("@account", "fis")),
|
|
||||||
Security: dbSec);
|
|
||||||
var names = dt.DataTable.Rows
|
var names = dt.DataTable.Rows
|
||||||
.Cast<System.Data.DataRow>()
|
.Cast<System.Data.DataRow>()
|
||||||
.OrderBy(r => dt.DataTable.Columns.Contains("order") ? r.nz("order") : "")
|
.OrderBy(r => dt.DataTable.Columns.Contains("order") ? r.nz("order") : "")
|
||||||
@@ -67,6 +69,7 @@ public class FuchsWidgetService : IWidgetService
|
|||||||
return await JSONAsync(names);
|
return await JSONAsync(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── "one" — full widget data for a single widget ──────────────────────────
|
||||||
private async Task<IActionResult> HandleWidgetOne(string userAccountId, DatabaseSecurity dbSec,
|
private async Task<IActionResult> HandleWidgetOne(string userAccountId, DatabaseSecurity dbSec,
|
||||||
HttpRequest request)
|
HttpRequest request)
|
||||||
{
|
{
|
||||||
@@ -75,26 +78,25 @@ public class FuchsWidgetService : IWidgetService
|
|||||||
|
|
||||||
var dt = await getSQLDatatable_async(
|
var dt = await getSQLDatatable_async(
|
||||||
"SELECT * FROM [dbo].[fis_getONEPersonWidgets] (@authuser) WHERE [short_name] = @shortname;",
|
"SELECT * FROM [dbo].[fis_getONEPersonWidgets] (@authuser) WHERE [short_name] = @shortname;",
|
||||||
_intranet.Intranet__SQLConnectionString,
|
Conn,
|
||||||
MakeParams(userAccountId,
|
Params(userAccountId,
|
||||||
SQL_VarChar("@shortname", shortName),
|
SQL_VarChar("@shortname", shortName),
|
||||||
SQL_VarChar("@account", "fis")),
|
SQL_VarChar("@account", "fis")),
|
||||||
Security: dbSec);
|
Security: dbSec);
|
||||||
|
|
||||||
if (dt.Count != 1) return new StatusCodeResult(404);
|
if (dt.Count != 1) return new StatusCodeResult(404);
|
||||||
var wdg = dt.FirstRow.toObjectDictionary();
|
var wdg = dt.FirstRow.toObjectDictionary();
|
||||||
return await BuildWidgetResponse(userAccountId, dbSec, wdg);
|
return await BuildWidgetResponse(userAccountId, dbSec, shortName, wdg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Generic widget by id ──────────────────────────────────────────────────
|
||||||
private async Task<IActionResult> HandleWidgetGeneric(string widgetId, string userAccountId,
|
private async Task<IActionResult> HandleWidgetGeneric(string widgetId, string userAccountId,
|
||||||
DatabaseSecurity dbSec)
|
DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
var pl = MakeParams(userAccountId, SQL_VarChar("@widget", widgetId, dbNull_IfEmpty: true));
|
var pl = Params(userAccountId, SQL_VarChar("@widget", widgetId, dbNull_IfEmpty: true));
|
||||||
var dset = await getSQLDataSet_async(
|
var dset = await getSQLDataSet_async(
|
||||||
"EXECUTE [dbo].[fds__getWidget] @widget, @authuser;",
|
"EXECUTE [dbo].[fds__getWidget] @widget, @authuser;",
|
||||||
_intranet.Intranet__SQLConnectionString, pl,
|
Conn, pl, tablenames: new[] { "admin", "data" }, Security: dbSec);
|
||||||
tablenames: new[] { "admin", "data" },
|
|
||||||
Security: dbSec);
|
|
||||||
return await JSONAsync(new
|
return await JSONAsync(new
|
||||||
{
|
{
|
||||||
admin = dset.Table("admin").FirstRow.toObjectDictionary(),
|
admin = dset.Table("admin").FirstRow.toObjectDictionary(),
|
||||||
@@ -102,28 +104,98 @@ public class FuchsWidgetService : IWidgetService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IActionResult> BuildWidgetResponse(string userAccountId,
|
// ── Widget renderer dispatcher ────────────────────────────────────────────
|
||||||
DatabaseSecurity dbSec, Dictionary<string, object?> wdg)
|
private async Task<IActionResult> BuildWidgetResponse(string userAccountId, DatabaseSecurity dbSec,
|
||||||
|
string shortName, Dictionary<string, object?> wdg)
|
||||||
{
|
{
|
||||||
string type = (wdg.nz("type", "") ?? "").ToLower();
|
string dbType = (wdg.nz("type", "") ?? "").ToLower();
|
||||||
string sql = wdg.nz("sql", "") ?? "";
|
string sql = wdg.nz("sql", "") ?? "";
|
||||||
|
var ropts = ParseRenderingOptions(wdg.nz("rendering_options", "") ?? "");
|
||||||
|
string name = wdg.nz("name", "") ?? "";
|
||||||
|
string descr = wdg.nz("description", "") ?? "";
|
||||||
|
|
||||||
if (type.StartsWith("sql") && !string.IsNullOrEmpty(sql))
|
object widgetData;
|
||||||
|
|
||||||
|
switch (dbType)
|
||||||
{
|
{
|
||||||
var dt = await getSQLDatatable_async(sql,
|
case "sql_table":
|
||||||
_intranet.Intranet__SQLConnectionString,
|
|
||||||
MakeParams(userAccountId), Security: dbSec);
|
|
||||||
return await JSONAsync(new
|
|
||||||
{
|
{
|
||||||
name = wdg.nz("name", ""),
|
var dt = await getSQLDatatable_async(sql, Conn, Params(userAccountId), Security: dbSec);
|
||||||
type,
|
widgetData = new
|
||||||
rows = dt.DataTable.Rows
|
{
|
||||||
.Cast<System.Data.DataRow>()
|
name,
|
||||||
.Select(r => r.toObjectDictionary())
|
description = descr,
|
||||||
.ToArray()
|
type = "table",
|
||||||
});
|
rendering_options = ropts,
|
||||||
|
columns = dt.DataTable.Columns.Cast<System.Data.DataColumn>()
|
||||||
|
.Select(c => c.ColumnName).ToArray(),
|
||||||
|
data = dt.DataTable.Rows.Cast<System.Data.DataRow>()
|
||||||
|
.Select(r => r.toObjectDictionary()).ToArray()
|
||||||
|
};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await JSONAsync(wdg);
|
case "sql_indicator":
|
||||||
|
{
|
||||||
|
var dt = await getSQLDatatable_async(sql, Conn, Params(userAccountId), Security: dbSec);
|
||||||
|
var firstRow = dt.DataTable.Rows.Count > 0
|
||||||
|
? dt.DataTable.Rows[0].toObjectDictionary()
|
||||||
|
: new Dictionary<string, object?>();
|
||||||
|
widgetData = new
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
description = descr,
|
||||||
|
type = "ind",
|
||||||
|
rendering_options = ropts,
|
||||||
|
data = new
|
||||||
|
{
|
||||||
|
status = firstRow.nz("status", "") ?? "",
|
||||||
|
value = firstRow.nz("value", "") ?? "",
|
||||||
|
label = firstRow.nz("label", "") ?? ""
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "html":
|
||||||
|
widgetData = new
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
description = descr,
|
||||||
|
type = "html",
|
||||||
|
rendering_options = ropts,
|
||||||
|
html = wdg.nz("html", "") ?? ""
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
widgetData = new
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
description = descr,
|
||||||
|
type = dbType,
|
||||||
|
rendering_options = ropts,
|
||||||
|
html = wdg.nz("html", "") ?? "",
|
||||||
|
url = wdg.nz("url", "") ?? "",
|
||||||
|
image = wdg.nz("image", "") ?? "",
|
||||||
|
data = (object)new
|
||||||
|
{
|
||||||
|
status = wdg.nz("status", "") ?? "",
|
||||||
|
value = wdg.nz("value", "") ?? "",
|
||||||
|
label = wdg.nz("label", "") ?? ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap under short_name key so the front-end can do response[wi]
|
||||||
|
return await JSONAsync(new Dictionary<string, object> { [shortName] = widgetData });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] ParseRenderingOptions(string raw) =>
|
||||||
|
string.IsNullOrWhiteSpace(raw)
|
||||||
|
? Array.Empty<string>()
|
||||||
|
: raw.Split(new[] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(s => s.Trim())
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
using Microsoft.Data.SqlClient;
|
|
||||||
|
|
||||||
namespace Fuchs.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Factory for creating SQL connections to the Fuchs database.
|
|
||||||
/// </summary>
|
|
||||||
public interface IDbConnectionFactory
|
|
||||||
{
|
|
||||||
/// <summary>Gets the primary FDS connection string.</summary>
|
|
||||||
string ConnectionString { get; }
|
|
||||||
|
|
||||||
/// <summary>Creates and opens a new SQL connection.</summary>
|
|
||||||
SqlConnection CreateConnection();
|
|
||||||
|
|
||||||
/// <summary>Creates a new SQL connection (not opened).</summary>
|
|
||||||
SqlConnection CreateClosedConnection();
|
|
||||||
}
|
|
||||||
@@ -13,8 +13,8 @@ public interface IInvoiceService
|
|||||||
/// <summary>Loads an existing invoice by ID.</summary>
|
/// <summary>Loads an existing invoice by ID.</summary>
|
||||||
Task<FdsInvoiceData> LoadInvoiceAsync(string id, string userAccountId, DatabaseSecurity dbSec);
|
Task<FdsInvoiceData> LoadInvoiceAsync(string id, string userAccountId, DatabaseSecurity dbSec);
|
||||||
|
|
||||||
/// <summary>Registers (creates or updates) an invoice from form data.</summary>
|
/// <summary>Registers (creates or updates) an invoice from a parsed data object.</summary>
|
||||||
Task<FdsInvoiceData> RegisterInvoiceAsync(object formData, bool change, string invId,
|
Task<FdsInvoiceData> RegisterInvoiceAsync(FdsInvoiceData invoice, bool change, string invId,
|
||||||
string userAccountId, DatabaseSecurity dbSec);
|
string userAccountId, DatabaseSecurity dbSec);
|
||||||
|
|
||||||
/// <summary>Generates a PDF document for an invoice.</summary>
|
/// <summary>Generates a PDF document for an invoice.</summary>
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ public interface IReminderService
|
|||||||
/// <summary>Loads an existing reminder by ID.</summary>
|
/// <summary>Loads an existing reminder by ID.</summary>
|
||||||
Task<FdsReminderData> LoadReminderAsync(string id, string userAccountId, DatabaseSecurity dbSec);
|
Task<FdsReminderData> LoadReminderAsync(string id, string userAccountId, DatabaseSecurity dbSec);
|
||||||
|
|
||||||
/// <summary>Registers (creates) a reminder from form data.</summary>
|
/// <summary>Registers (creates) a reminder from a parsed data object.</summary>
|
||||||
Task<FdsReminderData> RegisterReminderAsync(object formData, bool change, string remId,
|
Task<FdsReminderData> RegisterReminderAsync(FdsReminderData reminder, bool change, string remId,
|
||||||
string userAccountId, DatabaseSecurity dbSec);
|
string userAccountId, DatabaseSecurity dbSec);
|
||||||
|
|
||||||
/// <summary>Generates a PDF document for a reminder.</summary>
|
/// <summary>Generates a PDF document for a reminder.</summary>
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using OCORE.security;
|
using OCORE.security;
|
||||||
using OCORE.SQL;
|
|
||||||
|
|
||||||
namespace Fuchs.Services;
|
namespace Fuchs.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstraction for report processing.
|
/// Abstraction for SQL-driven report processing (HTML page / fragment / PNG chart).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IReportService
|
public interface IReportService
|
||||||
{
|
{
|
||||||
/// <summary>Processes a report request.</summary>
|
/// <summary>
|
||||||
Task<IActionResult> ProcessRequestAsync(string action, string id,
|
/// Processes a report request.
|
||||||
string userAccountId, DatabaseSecurity dbSec);
|
/// </summary>
|
||||||
|
/// <param name="fnc">Target function: "generic", "generic_content"/"gct", "chart", or a query-type name.</param>
|
||||||
|
/// <param name="reportId">Report name/id (the URL <c>code</c> segment).</param>
|
||||||
|
/// <param name="userAccountId">Authenticated user id.</param>
|
||||||
|
/// <param name="dbSec">Database security context.</param>
|
||||||
|
/// <param name="parameters">Merged query-string + form parameters.</param>
|
||||||
|
Task<IActionResult> ProcessRequestAsync(string fnc, string reportId,
|
||||||
|
string userAccountId, DatabaseSecurity dbSec, IDictionary<string, string> parameters);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +1,140 @@
|
|||||||
using Fuchs.intranet;
|
using System.Data;
|
||||||
|
using Fuchs.intranet;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MigraDoc.DocumentObjectModel;
|
using MigraDoc.DocumentObjectModel;
|
||||||
using MigraDoc.Rendering;
|
|
||||||
using OCORE.security;
|
using OCORE.security;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using OCORE.SQL;
|
using OCORE.SQL;
|
||||||
|
using static OCORE.commons;
|
||||||
|
using static OCORE.OCORE_dictionaries;
|
||||||
using static OCORE.SQL.sql;
|
using static OCORE.SQL.sql;
|
||||||
|
|
||||||
namespace Fuchs.Services;
|
namespace Fuchs.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoice service implementation. Extracts DB operations from <c>FdsInvoiceData</c>
|
/// Invoice service — load, register, render and store invoices.
|
||||||
/// and PDF generation into a proper DI service.
|
/// Replaces the controller-coupled, sync-over-async logic that previously lived
|
||||||
|
/// inside <see cref="FdsInvoiceData"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class InvoiceService : IInvoiceService
|
public class InvoiceService : IInvoiceService
|
||||||
{
|
{
|
||||||
private readonly Fuchs_intranet _intranet;
|
private readonly Fuchs_intranet _intranet;
|
||||||
private readonly IPdfService _pdfService;
|
private readonly IPdfService _pdf;
|
||||||
private readonly ILogger<InvoiceService> _logger;
|
private readonly ILogger<InvoiceService> _logger;
|
||||||
|
|
||||||
public InvoiceService(Fuchs_intranet intranet, IPdfService pdfService, ILogger<InvoiceService> logger)
|
public InvoiceService(Fuchs_intranet intranet, IPdfService pdf, ILogger<InvoiceService> logger)
|
||||||
{
|
{
|
||||||
_intranet = intranet;
|
_intranet = intranet;
|
||||||
_pdfService = pdfService;
|
_pdf = pdf;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string Conn => _intranet.Intranet__SQLConnectionString;
|
||||||
|
|
||||||
public async Task<FdsInvoiceData> LoadInvoiceAsync(string id, string userAccountId, DatabaseSecurity dbSec)
|
public async Task<FdsInvoiceData> LoadInvoiceAsync(string id, string userAccountId, DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
// TODO: Complete after FdsInvoiceData is refactored to remove IntranetController dependency
|
var inv = new FdsInvoiceData();
|
||||||
throw new NotImplementedException("InvoiceService.LoadInvoiceAsync pending FdsInvoiceData refactor.");
|
if (string.IsNullOrEmpty(id)) return inv;
|
||||||
|
|
||||||
|
var pl = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
SQL_VarChar("@authuser", userAccountId),
|
||||||
|
SQL_VarChar("@Id", id),
|
||||||
|
SQL_Bit("@includefile", false)
|
||||||
|
};
|
||||||
|
var dset = await getSQLDataSet_async(
|
||||||
|
"EXECUTE [dbo].[fds__getInvoice] @Id, @includefile, @authuser;",
|
||||||
|
Conn, pl, tablenames: new[] { "admin", "inv", "req", "itm" },
|
||||||
|
Security: dbSec, options: new FIS_SQLOptions());
|
||||||
|
if (!string.IsNullOrEmpty(dset.Exception))
|
||||||
|
_logger.LogError("LoadInvoiceAsync sql exception for {Id}: {Ex}", id, dset.Exception);
|
||||||
|
|
||||||
|
inv.InvoiceRegistration = new GenericObjectDictionary(dset.Table("inv").FirstRow.toObjectDictionary());
|
||||||
|
inv.IsDraft = inv.InvoiceRegistration.getItem("IsFinal", false) is not true;
|
||||||
|
return inv;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FdsInvoiceData> RegisterInvoiceAsync(object formData, bool change, string invId,
|
public async Task<FdsInvoiceData> RegisterInvoiceAsync(FdsInvoiceData invoice, bool change, string invId,
|
||||||
string userAccountId, DatabaseSecurity dbSec)
|
string userAccountId, DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("InvoiceService.RegisterInvoiceAsync pending FdsInvoiceData refactor.");
|
if (invoice.NewValues == null) return invoice;
|
||||||
|
|
||||||
|
var pl = new List<SqlParameter> { SQL_VarChar("@authuser", userAccountId) };
|
||||||
|
pl.AddRange(invoice.BuildInvoiceParams(change, invId));
|
||||||
|
|
||||||
|
var sqlParts = new List<string> { "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;");
|
||||||
|
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;");
|
||||||
|
sqlParts.Add("EXECUTE [dbo].[fds__createInvoice_Details] @Id, @InvoiceService_net, @InvoiceService_VAT, @InvoiceOptions, @authuser;");
|
||||||
|
}
|
||||||
|
if (invoice.RawProvisionLocation.Length > 0)
|
||||||
|
{
|
||||||
|
pl.Add(SQL_NVarChar("@ProvisionLocation",
|
||||||
|
string.Join("\n", invoice.RawProvisionLocation).LeftToFirst("<!--", emptyIfNotFound: false)));
|
||||||
|
sqlParts.Add("UPDATE [dbo].[fds__invoices] SET [ProvisionLocation] = LEFT(@ProvisionLocation,1000) WHERE [Id] = @Id AND [isFinal] = 0;");
|
||||||
|
}
|
||||||
|
|
||||||
|
var invdset = await getSQLDataSet_async(string.Join("\n", sqlParts),
|
||||||
|
Conn, pl, tablenames: new[] { "inv", "det", "req", "itm" },
|
||||||
|
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());
|
||||||
|
return invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Document GenerateInvoicePdf(FdsInvoiceData invoice, bool draft)
|
public Document GenerateInvoicePdf(FdsInvoiceData invoice, bool draft)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("InvoiceService.GenerateInvoicePdf pending FdsInvoiceData refactor.");
|
var reg = invoice.InvoiceRegistration;
|
||||||
|
var tb = new FuchsPdf.FdsTextBlocks
|
||||||
|
{
|
||||||
|
AdminRef = reg?.getString("Id") ?? "",
|
||||||
|
Address = reg?.getString("SendToAddress") is { Length: > 0 } sa
|
||||||
|
? sa.Replace("<br>", "\n").Replace("<br/>", "\n").Split('\n').Select(t => t.Trim()).ToArray()
|
||||||
|
: Array.Empty<string>(),
|
||||||
|
AdminUser = reg?.getString("UserNameFinalized") ?? "",
|
||||||
|
AdminUserEmail = reg?.getString("UserEmailFinalized") ?? "",
|
||||||
|
AdminDatumValue = reg?.getString("DateCreated") is { Length: > 0 } dc ? DateTime.Parse(dc) : DateTime.Now
|
||||||
|
};
|
||||||
|
// WriteLetter is effectively synchronous; no Task.Run thread-hop (safe: no sync context in ASP.NET Core).
|
||||||
|
var doc = _pdf.WriteLetterAsync(tb, draft).GetAwaiter().GetResult();
|
||||||
|
doc.Info.Title = reg?.getString("InvoiceTitle") ?? "";
|
||||||
|
_pdf.ApplyInvoice(doc, tb, invoice, draft);
|
||||||
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> RenderInvoicePdfBytesAsync(FdsInvoiceData invoice, bool draft)
|
public Task<byte[]> RenderInvoicePdfBytesAsync(FdsInvoiceData invoice, bool draft)
|
||||||
{
|
=> Task.FromResult(_pdf.DocToPdfBytes(GenerateInvoicePdf(invoice, draft)));
|
||||||
throw new NotImplementedException("InvoiceService.RenderInvoicePdfBytesAsync pending FdsInvoiceData refactor.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<byte[]> StoreInvoiceDocumentFileAsync(FdsInvoiceData invoice, bool draft,
|
public async Task<byte[]> StoreInvoiceDocumentFileAsync(FdsInvoiceData invoice, bool draft,
|
||||||
string userAccountId, DatabaseSecurity dbSec)
|
string userAccountId, DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("InvoiceService.StoreInvoiceDocumentFileAsync pending FdsInvoiceData refactor.");
|
byte[] ba;
|
||||||
|
try { ba = await RenderInvoicePdfBytesAsync(invoice, draft); }
|
||||||
|
catch (Exception ex) { _logger.LogError(ex, "StoreInvoiceDocumentFileAsync render failed for {Id}", invoice.Id); ba = Array.Empty<byte>(); }
|
||||||
|
if (ba.Length == 0) return Array.Empty<byte>();
|
||||||
|
|
||||||
|
var pl = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
SQL_VarChar("@authuser", userAccountId),
|
||||||
|
SQL_VarChar("@Id", invoice.Id),
|
||||||
|
new("@file", SqlDbType.VarBinary) { Value = ba }
|
||||||
|
};
|
||||||
|
bool r = await setSQLValue_async(
|
||||||
|
"EXECUTE [dbo].[fds__setInvoiceFile] @Id, @file;",
|
||||||
|
Conn, pl, Security: dbSec, options: new FIS_SQLOptions());
|
||||||
|
return r ? ba : Array.Empty<byte>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]?> GetInvoiceFileAsync(FdsInvoiceData invoice, bool draft,
|
public async Task<byte[]?> GetInvoiceFileAsync(FdsInvoiceData invoice, bool draft, fds.IFdsMfr mfr)
|
||||||
fds.IFdsMfr mfr)
|
|
||||||
{
|
{
|
||||||
if (invoice.InvoiceRegistration?.getItem("IsFinal", false) is true)
|
if (invoice.InvoiceRegistration?.getItem("IsFinal", false) is true)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,67 +1,169 @@
|
|||||||
using Fuchs.intranet;
|
using System.Data;
|
||||||
|
using Fuchs.intranet;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MigraDoc.DocumentObjectModel;
|
using MigraDoc.DocumentObjectModel;
|
||||||
using OCORE.security;
|
using OCORE.security;
|
||||||
using OCORE.SQL;
|
using OCORE.SQL;
|
||||||
using static OCORE.commons;
|
using static OCORE.commons;
|
||||||
|
using static OCORE.OCORE_dictionaries;
|
||||||
using static OCORE.SQL.sql;
|
using static OCORE.SQL.sql;
|
||||||
|
|
||||||
namespace Fuchs.Services;
|
namespace Fuchs.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reminder service implementation. Extracts DB operations from <c>FdsReminderData</c>
|
/// Reminder service — load, register, render and store reminders.
|
||||||
/// into a proper DI service.
|
/// Replaces the controller-coupled, sync-over-async logic that previously lived
|
||||||
|
/// inside <see cref="FdsReminderData"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ReminderService : IReminderService
|
public class ReminderService : IReminderService
|
||||||
{
|
{
|
||||||
private readonly Fuchs_intranet _intranet;
|
private readonly Fuchs_intranet _intranet;
|
||||||
private readonly IPdfService _pdfService;
|
private readonly IPdfService _pdf;
|
||||||
private readonly ILogger<ReminderService> _logger;
|
private readonly ILogger<ReminderService> _logger;
|
||||||
|
|
||||||
public ReminderService(Fuchs_intranet intranet, IPdfService pdfService, ILogger<ReminderService> logger)
|
public ReminderService(Fuchs_intranet intranet, IPdfService pdf, ILogger<ReminderService> logger)
|
||||||
{
|
{
|
||||||
_intranet = intranet;
|
_intranet = intranet;
|
||||||
_pdfService = pdfService;
|
_pdf = pdf;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string Conn => _intranet.Intranet__SQLConnectionString;
|
||||||
|
|
||||||
public async Task<FdsReminderData> LoadReminderAsync(string id, string userAccountId, DatabaseSecurity dbSec)
|
public async Task<FdsReminderData> LoadReminderAsync(string id, string userAccountId, DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("ReminderService.LoadReminderAsync pending FdsReminderData refactor.");
|
var rem = new FdsReminderData();
|
||||||
|
if (string.IsNullOrEmpty(id)) return rem;
|
||||||
|
|
||||||
|
var pl = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
SQL_VarChar("Id", id),
|
||||||
|
SQL_VarChar("@authuser", userAccountId),
|
||||||
|
SQL_Bit("@includefile", false)
|
||||||
|
};
|
||||||
|
var dset = await getSQLDataSet_async(
|
||||||
|
"EXECUTE [dbo].[fds__getReminder] @Id, @includefile, @authuser;",
|
||||||
|
Conn, pl, tablenames: new[] { "admin", "rem" },
|
||||||
|
Security: dbSec, options: new FIS_SQLOptions());
|
||||||
|
if (!string.IsNullOrEmpty(dset.Exception))
|
||||||
|
_logger.LogError("LoadReminderAsync sql exception for {Id}: {Ex}", id, dset.Exception);
|
||||||
|
|
||||||
|
rem.ReminderRegistration = new GenericObjectDictionary(dset.Table("rem").FirstRow.toObjectDictionary());
|
||||||
|
rem.IsDraft = rem.ReminderRegistration.getItem("IsFinal") is not true;
|
||||||
|
return rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FdsReminderData> RegisterReminderAsync(object formData, bool change, string remId,
|
public async Task<FdsReminderData> RegisterReminderAsync(FdsReminderData reminder, bool change, string remId,
|
||||||
string userAccountId, DatabaseSecurity dbSec)
|
string userAccountId, DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("ReminderService.RegisterReminderAsync pending FdsReminderData refactor.");
|
if (reminder.Rem == null || reminder.Rem.Count == 0) return reminder;
|
||||||
|
|
||||||
|
var pl = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
SQL_VarChar("@authuser", userAccountId),
|
||||||
|
SQL_VarChar("InvId", reminder.RawInvId),
|
||||||
|
SQL_Char("type", reminder.Rem["type"]?.ToString() ?? ""),
|
||||||
|
SQL_Float("amount", stringvalue: reminder.NewValues?.nz("amount") ?? ""),
|
||||||
|
SQL_Float("amount_payed", stringvalue: reminder.NewValues?.nz("amount_payed") ?? ""),
|
||||||
|
SQL_VarChar("SendToAddress", string.Join("\n", reminder.RawInvoiceAddress)),
|
||||||
|
SQL_NVarChar("SendToEmail", reminder.RawInvoiceEmail),
|
||||||
|
SQL_NVarChar("subject", reminder.NewValues?.getString("subject") ?? "", dbNull_IfEmpty: true),
|
||||||
|
SQL_NVarChar("text", reminder.NewValues?.nz("text") ?? "", dbNull_IfEmpty: true)
|
||||||
|
};
|
||||||
|
|
||||||
|
var sqlParts = new List<string> { "DECLARE @Id varchar(10);" };
|
||||||
|
if (!change || string.IsNullOrEmpty(remId))
|
||||||
|
sqlParts.Add("EXECUTE [dbo].[fds__createReminder] @InvId, @type, @amount, @amount_payed, @SendToAddress, @SendToEmail, @subject, @text, @authuser, @Id OUTPUT;");
|
||||||
|
else
|
||||||
|
pl.Add(SQL_VarChar("RemId", remId));
|
||||||
|
|
||||||
|
var remdset = await getSQLDataSet_async(string.Join("\n", sqlParts),
|
||||||
|
Conn, pl, tablenames: new[] { "rem" },
|
||||||
|
Security: dbSec, options: new FIS_SQLOptions());
|
||||||
|
if (!string.IsNullOrEmpty(remdset.Exception))
|
||||||
|
_logger.LogError("RegisterReminderAsync sql exception: {Ex}", remdset.Exception);
|
||||||
|
|
||||||
|
reminder.ReminderRegistration = new GenericObjectDictionary(remdset.Table("rem").FirstRow.toObjectDictionary());
|
||||||
|
return reminder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Document GenerateReminderPdf(FdsReminderData reminder, bool draft)
|
public Document GenerateReminderPdf(FdsReminderData reminder, bool draft)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("ReminderService.GenerateReminderPdf pending FdsReminderData refactor.");
|
var tb = new FuchsPdf.FdsTextBlocks
|
||||||
|
{
|
||||||
|
AdminRef = "",
|
||||||
|
Address = reminder.InvoiceAddress,
|
||||||
|
AdminUser = reminder.UserNameFinalized,
|
||||||
|
AdminUserEmail = reminder.UserEmailFinalized,
|
||||||
|
AdminDatumValue = reminder.DateCreated ?? DateTime.Now
|
||||||
|
};
|
||||||
|
if (!string.IsNullOrEmpty(reminder.RawCustomValues))
|
||||||
|
{
|
||||||
|
var o = new GenericObjectDictionary(reminder.RawCustomValues);
|
||||||
|
string oEmail = (string?)o.getItem("contactEmail") ?? "";
|
||||||
|
string oName = (string?)o.getItem("contactName") ?? "";
|
||||||
|
if (!string.IsNullOrEmpty(oEmail) || !string.IsNullOrEmpty(oName))
|
||||||
|
{
|
||||||
|
tb.AdminUser = oName;
|
||||||
|
tb.AdminUserEmail = oEmail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var doc = _pdf.WriteLetterAsync(tb, draft).GetAwaiter().GetResult();
|
||||||
|
doc.Info.Title = reminder.ReminderTitle;
|
||||||
|
_pdf.ApplyReminder(doc, tb, reminder, draft);
|
||||||
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> RenderReminderPdfBytesAsync(FdsReminderData reminder, bool draft)
|
public Task<byte[]> RenderReminderPdfBytesAsync(FdsReminderData reminder, bool draft)
|
||||||
{
|
=> Task.FromResult(_pdf.DocToPdfBytes(GenerateReminderPdf(reminder, draft)));
|
||||||
throw new NotImplementedException("ReminderService.RenderReminderPdfBytesAsync pending FdsReminderData refactor.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<byte[]> StoreReminderDocumentFileAsync(FdsReminderData reminder, bool draft,
|
public async Task<byte[]> StoreReminderDocumentFileAsync(FdsReminderData reminder, bool draft,
|
||||||
string userAccountId, DatabaseSecurity dbSec)
|
string userAccountId, DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("ReminderService.StoreReminderDocumentFileAsync pending FdsReminderData refactor.");
|
byte[] ba;
|
||||||
|
try { ba = await RenderReminderPdfBytesAsync(reminder, draft); }
|
||||||
|
catch (Exception ex) { _logger.LogError(ex, "StoreReminderDocumentFileAsync render failed for {Id}", reminder.Id); ba = Array.Empty<byte>(); }
|
||||||
|
if (ba.Length == 0) return Array.Empty<byte>();
|
||||||
|
|
||||||
|
var pl = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
SQL_VarChar("Id", reminder.Id),
|
||||||
|
SQL_VarChar("@authuser", userAccountId),
|
||||||
|
new("@file", SqlDbType.VarBinary) { Value = ba }
|
||||||
|
};
|
||||||
|
bool r = await setSQLValue_async(
|
||||||
|
"EXECUTE [dbo].[fds__setReminderFile] @Id, @file;",
|
||||||
|
Conn, pl, Security: dbSec, options: new FIS_SQLOptions());
|
||||||
|
return r ? ba : Array.Empty<byte>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> GetReminderFileAsync(FdsReminderData reminder, bool draft,
|
public async Task<byte[]> GetReminderFileAsync(FdsReminderData reminder, bool draft,
|
||||||
fds.IFdsMfr mfr, string userAccountId, DatabaseSecurity dbSec)
|
fds.IFdsMfr mfr, string userAccountId, DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("ReminderService.GetReminderFileAsync pending FdsReminderData refactor.");
|
if (reminder.ReminderRegistration?.getItem("IsFinal", false) is true)
|
||||||
|
{
|
||||||
|
if (!reminder.ReminderRegistration.ContainsKey("hasFile") ||
|
||||||
|
reminder.ReminderRegistration.getItem("hasFile", false) is not true)
|
||||||
|
await StoreReminderDocumentFileAsync(reminder, draft, userAccountId, dbSec);
|
||||||
|
byte[]? ba = null;
|
||||||
|
mfr.GetFdsDoc(ref ba, reminder.Id, "reminder");
|
||||||
|
return ba ?? Array.Empty<byte>();
|
||||||
|
}
|
||||||
|
return await RenderReminderPdfBytesAsync(reminder, draft);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(FileInfo? file, byte[]? content)> GetStoredFileAsync(string reminderId,
|
public async Task<(FileInfo? file, byte[]? content)> GetStoredFileAsync(string reminderId,
|
||||||
string userAccountId, DatabaseSecurity dbSec)
|
string userAccountId, DatabaseSecurity dbSec)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("ReminderService.GetStoredFileAsync pending FdsReminderData refactor.");
|
var dset = await getSQLDataSet_async(
|
||||||
|
"SELECT TOP(1) * FROM [dbo].[fds__reminder] WHERE [Id] = @Id AND [file] is not null;",
|
||||||
|
Conn,
|
||||||
|
new List<SqlParameter> { SQL_VarChar("@authuser", userAccountId), SQL_VarChar("@Id", reminderId) },
|
||||||
|
Security: dbSec, options: new FIS_SQLOptions());
|
||||||
|
var row = dset.FirstTable().FirstRow.toObjectDictionary();
|
||||||
|
if (row.Count > 0 && !string.IsNullOrEmpty(row.nz("DocumentName")) && row.no("file", null!) is byte[] b)
|
||||||
|
return (new FileInfo(row.nz("DocumentName")), b);
|
||||||
|
return (null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
using System.Data;
|
|
||||||
using programmersdigest.MT940Parser;
|
|
||||||
|
|
||||||
namespace Fuchs.intranet;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MT940 bank statement parser helpers.
|
|
||||||
/// </summary>
|
|
||||||
public static class Banking
|
|
||||||
{
|
|
||||||
public static string DebitCreditMarkAbb(DebitCreditMark mark) => mark switch
|
|
||||||
{
|
|
||||||
DebitCreditMark.Credit => "C",
|
|
||||||
DebitCreditMark.Debit => "D",
|
|
||||||
DebitCreditMark.ReverseCredit => "RC",
|
|
||||||
DebitCreditMark.ReverseDebit => "RD",
|
|
||||||
_ => ""
|
|
||||||
};
|
|
||||||
|
|
||||||
public static DataTable ParseToDatatable(Stream stream, DataTable? schemaDatatable = null,
|
|
||||||
ILogger? logger = null)
|
|
||||||
{
|
|
||||||
logger ??= NullLogger.Instance;
|
|
||||||
var tbl = schemaDatatable?.Clone() ?? BuildDefaultSchema();
|
|
||||||
|
|
||||||
void SetNfo(DataRow nr, string key, object? value)
|
|
||||||
{
|
|
||||||
if (tbl.Columns.Contains(key) && value != null)
|
|
||||||
nr[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var ps = new Parser(stream: stream);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var statement in ps.Parse())
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(statement.AccountIdentification)) continue;
|
|
||||||
foreach (var line in statement.Lines)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var nr = tbl.NewRow();
|
|
||||||
SetNfo(nr, "AccountIdentification", statement.AccountIdentification);
|
|
||||||
if (line.Amount.HasValue) SetNfo(nr, "Amount", line.Amount);
|
|
||||||
if (line.EntryDate.HasValue) SetNfo(nr, "EntryDate", line.EntryDate);
|
|
||||||
if (line.FundsCode.HasValue) SetNfo(nr, "FundsCode", line.FundsCode.ToString());
|
|
||||||
SetNfo(nr, "BankReference", line.BankReference);
|
|
||||||
|
|
||||||
var info = line.InformationToOwner;
|
|
||||||
SetNfo(nr, "AccountNumberOfPayer", info.AccountNumberOfPayer);
|
|
||||||
SetNfo(nr, "BankCodeOfPayer", info.BankCodeOfPayer);
|
|
||||||
SetNfo(nr, "CompensationAmount", info.CompensationAmount);
|
|
||||||
SetNfo(nr, "CreditorReference", info.CreditorReference);
|
|
||||||
SetNfo(nr, "CreditorsReferenceParty", info.CreditorsReferenceParty);
|
|
||||||
SetNfo(nr, "CustomerReference", info.CustomerReference);
|
|
||||||
SetNfo(nr, "EndToEndReference", info.EndToEndReference);
|
|
||||||
SetNfo(nr, "JournalNumber", info.JournalNumber);
|
|
||||||
SetNfo(nr, "MandateReference", info.MandateReference);
|
|
||||||
SetNfo(nr, "NameOfPayer", info.NameOfPayer);
|
|
||||||
SetNfo(nr, "OriginalAmount", info.OriginalAmount);
|
|
||||||
SetNfo(nr, "OriginatorsIdentificationCode", info.OriginatorsIdentificationCode);
|
|
||||||
SetNfo(nr, "PayersReferenceParty", info.PayersReferenceParty);
|
|
||||||
SetNfo(nr, "PostingText", info.PostingText);
|
|
||||||
SetNfo(nr, "SepaRemittanceInformation", info.SepaRemittanceInformation);
|
|
||||||
if (info.TextKeyAddition.HasValue) SetNfo(nr, "TextKeyAddition", info.TextKeyAddition);
|
|
||||||
SetNfo(nr, "TransactionCode", info.TransactionCode);
|
|
||||||
SetNfo(nr, "IsUnstructuredData", info.IsUnstructuredData);
|
|
||||||
SetNfo(nr, "UnstructuredData", info.UnstructuredData);
|
|
||||||
SetNfo(nr, "UnstructuredRemittanceInformation",info.UnstructuredRemittanceInformation);
|
|
||||||
|
|
||||||
SetNfo(nr, "DebitCreditMark", DebitCreditMarkAbb(line.Mark));
|
|
||||||
SetNfo(nr, "SupplementaryDetails",line.SupplementaryDetails);
|
|
||||||
SetNfo(nr, "TransactionTypeIdCode",line.TransactionTypeIdCode);
|
|
||||||
SetNfo(nr, "ValueDate", line.ValueDate);
|
|
||||||
|
|
||||||
tbl.Rows.Add(nr);
|
|
||||||
}
|
|
||||||
catch (Exception ex) { logger.LogWarning(ex, "MT940 line parse error — account={Account}", statement.AccountIdentification); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex) { logger.LogError(ex, "MT940 statement parse failed."); }
|
|
||||||
|
|
||||||
tbl.AcceptChanges();
|
|
||||||
return tbl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DataTable BuildDefaultSchema()
|
|
||||||
{
|
|
||||||
var t = new DataTable();
|
|
||||||
var cols = t.Columns;
|
|
||||||
cols.Add("AccountIdentification", typeof(string));
|
|
||||||
cols.Add("Amount", typeof(decimal));
|
|
||||||
cols.Add("BankReference", typeof(string));
|
|
||||||
cols.Add("EntryDate", typeof(DateTime));
|
|
||||||
cols.Add("FundsCode", typeof(string));
|
|
||||||
cols.Add("AccountNumberOfPayer", typeof(string));
|
|
||||||
cols.Add("BankCodeOfPayer", typeof(string));
|
|
||||||
cols.Add("CompensationAmount", typeof(string));
|
|
||||||
cols.Add("CreditorReference", typeof(string));
|
|
||||||
cols.Add("CreditorsReferenceParty", typeof(string));
|
|
||||||
cols.Add("CustomerReference", typeof(string));
|
|
||||||
cols.Add("EndToEndReference", typeof(string));
|
|
||||||
cols.Add("JournalNumber", typeof(string));
|
|
||||||
cols.Add("MandateReference", typeof(string));
|
|
||||||
cols.Add("NameOfPayer", typeof(string));
|
|
||||||
cols.Add("OriginalAmount", typeof(string));
|
|
||||||
cols.Add("OriginatorsIdentificationCode", typeof(string));
|
|
||||||
cols.Add("PayersReferenceParty", typeof(string));
|
|
||||||
cols.Add("PostingText", typeof(string));
|
|
||||||
cols.Add("SepaRemittanceInformation", typeof(string));
|
|
||||||
cols.Add("TextKeyAddition", typeof(int));
|
|
||||||
cols.Add("TransactionCode", typeof(int));
|
|
||||||
cols.Add("IsUnstructuredData", typeof(bool));
|
|
||||||
cols.Add("UnstructuredData", typeof(string));
|
|
||||||
cols.Add("UnstructuredRemittanceInformation", typeof(string));
|
|
||||||
cols.Add("DebitCreditMark", typeof(string));
|
|
||||||
cols.Add("SupplementaryDetails", typeof(string));
|
|
||||||
cols.Add("TransactionTypeIdCode", typeof(string));
|
|
||||||
cols.Add("ValueDate", typeof(DateTime));
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+23
-157
@@ -1,10 +1,5 @@
|
|||||||
using System.Data;
|
|
||||||
using Fuchs.Controllers;
|
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using MigraDoc.DocumentObjectModel;
|
|
||||||
using MigraDoc.Rendering;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OCORE.SQL;
|
|
||||||
using static OCORE.OCORE_dictionaries;
|
using static OCORE.OCORE_dictionaries;
|
||||||
using static OCORE.SQL.sql;
|
using static OCORE.SQL.sql;
|
||||||
using static OCORE.commons;
|
using static OCORE.commons;
|
||||||
@@ -12,25 +7,26 @@ using static OCORE.commons;
|
|||||||
namespace Fuchs.intranet;
|
namespace Fuchs.intranet;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encapsulates invoice (Rechnung) data. Converted from VB fds__invoice_data class.
|
/// Invoice (Rechnung) data holder. Converted from VB fds__invoice_data.
|
||||||
|
/// Pure data + parameter mapping — all persistence and PDF generation now live
|
||||||
|
/// in <see cref="Fuchs.Services.IInvoiceService"/> (no controller coupling).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FdsInvoiceData
|
public class FdsInvoiceData
|
||||||
{
|
{
|
||||||
private readonly JObject? _base;
|
private readonly JObject? _base;
|
||||||
private Document? _letter;
|
|
||||||
|
|
||||||
public GenericObjectDictionary? Admin { get; private set; }
|
public GenericObjectDictionary? Admin { get; private set; }
|
||||||
public GenericObjectDictionary? NewValues { get; private set; }
|
public GenericObjectDictionary? NewValues { get; private set; }
|
||||||
public GenericObjectDictionary? Sms { get; private set; }
|
public GenericObjectDictionary? Sms { get; private set; }
|
||||||
public List<Dictionary<string, object>>? Req { get; private set; }
|
public List<Dictionary<string, object>>? Req { get; private set; }
|
||||||
|
|
||||||
public GenericObjectDictionary? InvoiceRegistration { get; private set; }
|
public GenericObjectDictionary? InvoiceRegistration { get; internal set; }
|
||||||
public bool IsDraft { get; private set; } = true;
|
public bool IsDraft { get; internal set; } = true;
|
||||||
|
|
||||||
public string Id => InvoiceRegistration?.getString("Id") ?? "";
|
public string Id => InvoiceRegistration?.getString("Id") ?? "";
|
||||||
public string PaymentTerms => InvoiceRegistration?.getString("PaymentTerm") ?? "";
|
public string PaymentTerms => InvoiceRegistration?.getString("PaymentTerm") ?? "";
|
||||||
|
|
||||||
// -- PDF-facing properties (used by FuchsPdf.ApplyInvoice) ----------------
|
// -- PDF-facing properties (used by IPdfService.ApplyInvoice) -------------
|
||||||
public string InvoiceType =>
|
public string InvoiceType =>
|
||||||
InvoiceRegistration?.getString("InvoiceType").Substr(0, 1) ?? "R";
|
InvoiceRegistration?.getString("InvoiceType").Substr(0, 1) ?? "R";
|
||||||
public string InvoiceId =>
|
public string InvoiceId =>
|
||||||
@@ -56,7 +52,7 @@ public class FdsInvoiceData
|
|||||||
{
|
{
|
||||||
IEnumerable<Dictionary<string, object?>>? itms =
|
IEnumerable<Dictionary<string, object?>>? itms =
|
||||||
itmsObj as IEnumerable<Dictionary<string, object?>>
|
itmsObj as IEnumerable<Dictionary<string, object?>>
|
||||||
?? (itmsObj is Newtonsoft.Json.Linq.JArray ja
|
?? (itmsObj is JArray ja
|
||||||
? ja.ToObject<List<Dictionary<string, object?>>>()
|
? ja.ToObject<List<Dictionary<string, object?>>>()
|
||||||
: null);
|
: null);
|
||||||
if (itms != null) result.AddRange(itms);
|
if (itms != null) result.AddRange(itms);
|
||||||
@@ -73,33 +69,33 @@ public class FdsInvoiceData
|
|||||||
{
|
{
|
||||||
var result = new Dictionary<string, Dictionary<string, object?>>();
|
var result = new Dictionary<string, Dictionary<string, object?>>();
|
||||||
if (InvoiceRegistration == null) return result;
|
if (InvoiceRegistration == null) return result;
|
||||||
// Primary VAT slot
|
|
||||||
if (FuchsPdf.ParseDec(InvoiceRegistration.getItem("InvoiceVAT_1"), out decimal ust1)
|
if (FuchsPdf.ParseDec(InvoiceRegistration.getItem("InvoiceVAT_1"), out decimal ust1)
|
||||||
&& FuchsPdf.ParseDec(InvoiceRegistration.getItem("InvoiceVAT_net1"), out decimal net1)
|
&& FuchsPdf.ParseDec(InvoiceRegistration.getItem("InvoiceVAT_net1"), out decimal net1)
|
||||||
&& !(ust1 == 0 && net1 == 0))
|
&& !(ust1 == 0 && net1 == 0))
|
||||||
result[ust1.ToString("0.##")] = new Dictionary<string, object?>
|
result[ust1.ToString("0.##")] = new Dictionary<string, object?> { ["vat_amount"] = net1 };
|
||||||
{ ["vat_amount"] = net1 };
|
|
||||||
// Secondary VAT slot
|
|
||||||
if (FuchsPdf.ParseDec(InvoiceRegistration.getItem("InvoiceVAT_2"), out decimal ust2)
|
if (FuchsPdf.ParseDec(InvoiceRegistration.getItem("InvoiceVAT_2"), out decimal ust2)
|
||||||
&& FuchsPdf.ParseDec(InvoiceRegistration.getItem("InvoiceVAT_net2"), out decimal net2)
|
&& FuchsPdf.ParseDec(InvoiceRegistration.getItem("InvoiceVAT_net2"), out decimal net2)
|
||||||
&& !(ust2 == 0 && net2 == 0))
|
&& !(ust2 == 0 && net2 == 0))
|
||||||
result[ust2.ToString("0.##")] = new Dictionary<string, object?>
|
result[ust2.ToString("0.##")] = new Dictionary<string, object?> { ["vat_amount"] = net2 };
|
||||||
{ ["vat_amount"] = net2 };
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Raw properties --------------------------------------------------------
|
// -- Raw form properties (used by parameter mapping) -----------------------
|
||||||
private string RawInvoiceAddress => NewValues?.nz("invoiceaddress") ?? "";
|
internal string RawInvoiceAddress => NewValues?.nz("invoiceaddress") ?? "";
|
||||||
private string RawInvoiceEmail => NewValues?.nz("invoiceemail").Trim() ?? "";
|
internal string RawInvoiceEmail => NewValues?.nz("invoiceemail").Trim() ?? "";
|
||||||
private string RawCustomValues => NewValues?.nz("CustomValues").Trim() ?? "";
|
internal string RawCustomValues => NewValues?.nz("CustomValues").Trim() ?? "";
|
||||||
private string RawProvisionPeriod => NewValues?.nz("provisionperiod") ?? "";
|
internal string RawProvisionPeriod => NewValues?.nz("provisionperiod") ?? "";
|
||||||
private string[] RawProvisionLocation =>
|
internal string[] RawProvisionLocation =>
|
||||||
NewValues?.nz("provisionlocation") is { Length: > 0 } s
|
NewValues?.nz("provisionlocation") is { Length: > 0 } s
|
||||||
? s.Replace("\r\n", "\n").Split('\n').Select(t => t.Trim()).Where(t => t != "").ToArray()
|
? s.Replace("\r\n", "\n").Split('\n').Select(t => t.Trim()).Where(t => t != "").ToArray()
|
||||||
: Array.Empty<string>();
|
: Array.Empty<string>();
|
||||||
|
|
||||||
// -- Ctors -----------------------------------------------------------------
|
// -- Ctors -----------------------------------------------------------------
|
||||||
|
/// <summary>Empty instance (used when loading from the DB via the service).</summary>
|
||||||
|
public FdsInvoiceData() { }
|
||||||
|
|
||||||
|
/// <summary>Parses form data (admin/new/sms/req) into the data object.</summary>
|
||||||
public FdsInvoiceData(object ctd)
|
public FdsInvoiceData(object ctd)
|
||||||
{
|
{
|
||||||
_base = ctd as JObject;
|
_base = ctd as JObject;
|
||||||
@@ -113,146 +109,16 @@ public class FdsInvoiceData
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public FdsInvoiceData(string id, IntranetController ctrl) => RegisterInvoice(id, ctrl);
|
// -- Parameter mapping (consumed by InvoiceService.RegisterInvoiceAsync) ---
|
||||||
|
internal List<SqlParameter> BuildInvoiceParams(bool change, string invId)
|
||||||
// -- PDF -------------------------------------------------------------------
|
|
||||||
public Document InvoicePDF(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
if (_letter != null) return _letter;
|
|
||||||
if (InvoiceRegistration == null) RegisterInvoice(Id, ctrl);
|
|
||||||
var tb = new FuchsPdf.FdsTextBlocks
|
|
||||||
{
|
|
||||||
AdminRef = InvoiceRegistration?.getString("Id") ?? "",
|
|
||||||
Address = InvoiceRegistration?.getString("SendToAddress") is { Length: > 0 } sa
|
|
||||||
? sa.Replace("<br>", "\n").Replace("<br/>", "\n").Split('\n').Select(t => t.Trim()).ToArray()
|
|
||||||
: Array.Empty<string>(),
|
|
||||||
AdminUser = InvoiceRegistration?.getString("UserNameFinalized") ?? "",
|
|
||||||
AdminUserEmail = InvoiceRegistration?.getString("UserEmailFinalized") ?? "",
|
|
||||||
AdminDatumValue = InvoiceRegistration?.getString("DateCreated") is { Length: > 0 } dc ? DateTime.Parse(dc) : DateTime.Now
|
|
||||||
};
|
|
||||||
_letter = Task.Run(async () => await FuchsPdf.WriteLetter(tb, draft: IsDraft, locale: FuchsPdf.DeCulture)).Result;
|
|
||||||
_letter.Info.Title = InvoiceRegistration?.getString("InvoiceTitle") ?? "";
|
|
||||||
FuchsPdf.ApplyInvoice(_letter, tb, this, draft: IsDraft);
|
|
||||||
return _letter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- File operations -------------------------------------------------------
|
|
||||||
public async Task<byte[]?> GetInvoiceFile(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
if (InvoiceRegistration?.getItem("IsFinal", false) is true)
|
|
||||||
{
|
|
||||||
byte[]? ba = null;
|
|
||||||
ctrl._mfr.GetFdsDoc(ref ba, Id, "invoice");
|
|
||||||
return ba;
|
|
||||||
}
|
|
||||||
return await RenderToPdfBytes(ctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<byte[]> StoreInvoiceDocumentFile(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
byte[] ba;
|
|
||||||
try { ba = await RenderToPdfBytes(ctrl); }
|
|
||||||
catch { ba = Array.Empty<byte>(); }
|
|
||||||
if (ba.Length == 0) return Array.Empty<byte>();
|
|
||||||
|
|
||||||
var pl = ctrl.StdParamlist(SQL_VarChar("@Id", Id));
|
|
||||||
pl.Add(new SqlParameter("@file", SqlDbType.VarBinary) { Value = ba });
|
|
||||||
bool r = await setSQLValue_async(
|
|
||||||
"EXECUTE [dbo].[fds__setInvoiceFile] @Id, @file;",
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString, pl,
|
|
||||||
Security: ctrl.DbSec, options: new FIS_SQLOptions());
|
|
||||||
return r ? ba : Array.Empty<byte>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<byte[]> RenderToPdfBytes(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
var pdfrend = new PdfDocumentRenderer() { Document = InvoicePDF(ctrl) };
|
|
||||||
pdfrend.RenderDocument();
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
pdfrend.PdfDocument.Save(ms, false);
|
|
||||||
ms.Position = 0;
|
|
||||||
return OCORE.pdf._pdf.pdfAFileContent(ms.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Registration ----------------------------------------------------------
|
|
||||||
public void RegisterInvoice(IntranetController ctrl, bool change, string invId)
|
|
||||||
{
|
|
||||||
if (NewValues == null) return;
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pl = ctrl.StdParamlist();
|
|
||||||
pl.AddRange(BuildInvoiceParams(change, invId));
|
|
||||||
|
|
||||||
var sqlParts = new List<string> { "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;");
|
|
||||||
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;");
|
|
||||||
sqlParts.Add("EXECUTE [dbo].[fds__createInvoice_Details] @Id, @InvoiceService_net, @InvoiceService_VAT, @InvoiceOptions, @authuser;");
|
|
||||||
}
|
|
||||||
if (RawProvisionLocation.Length > 0)
|
|
||||||
{
|
|
||||||
pl.Add(SQL_NVarChar("@ProvisionLocation",
|
|
||||||
string.Join("\n", RawProvisionLocation).LeftToFirst("<!--", emptyIfNotFound: false)));
|
|
||||||
sqlParts.Add("UPDATE [dbo].[fds__invoices] SET [ProvisionLocation] = LEFT(@ProvisionLocation,1000) WHERE [Id] = @Id AND [isFinal] = 0;");
|
|
||||||
}
|
|
||||||
|
|
||||||
var invdset = await getSQLDataSet_async(string.Join("\n", sqlParts),
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString, pl,
|
|
||||||
tablenames: new[] { "inv", "det", "req", "itm" },
|
|
||||||
Security: ctrl.DbSec, options: new FIS_SQLOptions());
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(invdset.Exception))
|
|
||||||
ctrl._intranet.debug_log("FdsInvoiceData.RegisterInvoice - sql exception",
|
|
||||||
data: new { exception = invdset.Exception });
|
|
||||||
|
|
||||||
InvoiceRegistration = new GenericObjectDictionary(invdset.Table("inv").FirstRow.toObjectDictionary());
|
|
||||||
}
|
|
||||||
catch (Exception ex) { ctrl._intranet.debug_log("FdsInvoiceData.RegisterInvoice", ex: ex); }
|
|
||||||
}).Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterInvoice(string id, IntranetController ctrl)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(id)) return;
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pl = ctrl.StdParamlist(SQL_VarChar("@Id", id));
|
|
||||||
pl.Add(SQL_Bit("@includefile", false));
|
|
||||||
var invdset = await getSQLDataSet_async(
|
|
||||||
"EXECUTE [dbo].[fds__getInvoice] @Id, @includefile, @authuser;",
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString, pl,
|
|
||||||
tablenames: new[] { "admin", "inv", "req", "itm" },
|
|
||||||
Security: ctrl.DbSec, options: new FIS_SQLOptions());
|
|
||||||
if (!string.IsNullOrEmpty(invdset.Exception))
|
|
||||||
ctrl._intranet.debug_log("FdsInvoiceData.RegisterInvoice(id) - sql exception",
|
|
||||||
data: new { exception = invdset.Exception });
|
|
||||||
InvoiceRegistration = new GenericObjectDictionary(invdset.Table("inv").FirstRow.toObjectDictionary());
|
|
||||||
IsDraft = InvoiceRegistration.getItem("IsFinal", false) is not true;
|
|
||||||
}
|
|
||||||
catch (Exception ex) { ctrl._intranet.debug_log("FdsInvoiceData.RegisterInvoice(id)", ex: ex); }
|
|
||||||
}).Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Param builder ---------------------------------------------------------
|
|
||||||
private List<SqlParameter> BuildInvoiceParams(bool change, string invId)
|
|
||||||
{
|
{
|
||||||
|
_ = change; _ = invId;
|
||||||
var vatsDic = new Dictionary<string, string>();
|
var vatsDic = new Dictionary<string, string>();
|
||||||
if (Req != null)
|
if (Req != null)
|
||||||
{
|
{
|
||||||
foreach (var rq in Req)
|
foreach (var rq in Req)
|
||||||
{
|
{
|
||||||
if (rq.TryGetValue("items", out var itmsObj) &&
|
if (rq.TryGetValue("items", out var itmsObj) && itmsObj is List<object> itms)
|
||||||
itmsObj is List<object> itms)
|
|
||||||
{
|
{
|
||||||
foreach (var itm in itms.OfType<Dictionary<string, object?>>())
|
foreach (var itm in itms.OfType<Dictionary<string, object?>>())
|
||||||
{
|
{
|
||||||
|
|||||||
+14
-167
@@ -1,42 +1,35 @@
|
|||||||
using System.Data;
|
|
||||||
using Fuchs.Controllers;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using MigraDoc.DocumentObjectModel;
|
|
||||||
using MigraDoc.Rendering;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OCORE.SQL;
|
|
||||||
using static OCORE.OCORE_dictionaries;
|
using static OCORE.OCORE_dictionaries;
|
||||||
using static OCORE.SQL.sql;
|
|
||||||
using static OCORE.commons;
|
using static OCORE.commons;
|
||||||
|
|
||||||
namespace Fuchs.intranet;
|
namespace Fuchs.intranet;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encapsulates a reminder (Zahlungserinnerung) data object.
|
/// Reminder (Zahlungserinnerung) data holder. Converted from VB fds__reminder_data.
|
||||||
/// Converted from VB fds__reminder_data class.
|
/// Pure data — persistence and PDF generation live in
|
||||||
|
/// <see cref="Fuchs.Services.IReminderService"/> (no controller coupling).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FdsReminderData
|
public class FdsReminderData
|
||||||
{
|
{
|
||||||
private readonly JObject? _base;
|
private readonly JObject? _base;
|
||||||
private Document? _letter;
|
|
||||||
|
|
||||||
public GenericObjectDictionary? NewValues { get; private set; }
|
public GenericObjectDictionary? NewValues { get; private set; }
|
||||||
public GenericObjectDictionary? Rem { get; private set; }
|
public GenericObjectDictionary? Rem { get; private set; }
|
||||||
public GenericObjectDictionary? ReminderRegistration { get; private set; }
|
public GenericObjectDictionary? ReminderRegistration { get; internal set; }
|
||||||
public bool IsDraft { get; private set; } = true;
|
public bool IsDraft { get; internal set; } = true;
|
||||||
|
|
||||||
public string Id => ReminderRegistration?.getString("Id") ?? "";
|
public string Id => ReminderRegistration?.getString("Id") ?? "";
|
||||||
|
|
||||||
// -- Raw props from form data ---------------------------------------------
|
// -- Raw props from form data ---------------------------------------------
|
||||||
public string[] RawInvoiceAddress => NewValues?.nz("invoiceaddress") is { Length: > 0 } s
|
internal string[] RawInvoiceAddress => NewValues?.nz("invoiceaddress") is { Length: > 0 } s
|
||||||
? s.Replace("<br>", "\n").Replace("<br/>", "\n").Replace("<br />", "\n")
|
? s.Replace("<br>", "\n").Replace("<br/>", "\n").Replace("<br />", "\n")
|
||||||
.Replace("\r\n", "\n").Replace("\n\n", "\n").Split('\n')
|
.Replace("\r\n", "\n").Replace("\n\n", "\n").Split('\n')
|
||||||
.Select(t => System.Web.HttpUtility.HtmlDecode(t.Trim())).Where(t => t != "").ToArray()
|
.Select(t => System.Web.HttpUtility.HtmlDecode(t.Trim())).Where(t => t != "").ToArray()
|
||||||
: Array.Empty<string>();
|
: Array.Empty<string>();
|
||||||
|
|
||||||
public string RawInvoiceEmail => NewValues?.nz("invoiceemail").Trim() ?? "";
|
internal string RawInvoiceEmail => NewValues?.nz("invoiceemail").Trim() ?? "";
|
||||||
public string RawInvId => Rem?.nz("invid").ne(Rem?.nz("InvId") ?? "").Trim() ?? "";
|
internal string RawInvId => Rem?.nz("invid").ne(Rem?.nz("InvId") ?? "").Trim() ?? "";
|
||||||
public string RawCustomValues => NewValues?.nz("CustomValues").Trim() ?? "";
|
internal string RawCustomValues => NewValues?.nz("CustomValues").Trim() ?? "";
|
||||||
|
|
||||||
// -- Computed props from registration -------------------------------------
|
// -- Computed props from registration -------------------------------------
|
||||||
public DateTime? DateCreated => ReminderRegistration?.getString("DateCreated") is { Length: > 0 } d ? DateTime.Parse(d) : null;
|
public DateTime? DateCreated => ReminderRegistration?.getString("DateCreated") is { Length: > 0 } d ? DateTime.Parse(d) : null;
|
||||||
@@ -59,11 +52,10 @@ public class FdsReminderData
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (ReminderRegistration == null) return new List<Dictionary<string, object?>>();
|
if (ReminderRegistration == null) return new List<Dictionary<string, object?>>();
|
||||||
// Items are stored as nested JSON under "invoices" key in the registration
|
|
||||||
var raw = ReminderRegistration.getItem("invoices");
|
var raw = ReminderRegistration.getItem("invoices");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (raw is Newtonsoft.Json.Linq.JArray ja)
|
if (raw is JArray ja)
|
||||||
return ja.ToObject<List<Dictionary<string, object?>>>() ?? new();
|
return ja.ToObject<List<Dictionary<string, object?>>>() ?? new();
|
||||||
if (raw is string s && s.StartsWith("["))
|
if (raw is string s && s.StartsWith("["))
|
||||||
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object?>>>(s) ?? new();
|
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object?>>>(s) ?? new();
|
||||||
@@ -74,6 +66,10 @@ public class FdsReminderData
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------- Ctors ----------------------------------
|
// -------------------------------- Ctors ----------------------------------
|
||||||
|
/// <summary>Empty instance (used when loading from the DB via the service).</summary>
|
||||||
|
public FdsReminderData() { }
|
||||||
|
|
||||||
|
/// <summary>Parses form data (new/rem) into the data object.</summary>
|
||||||
public FdsReminderData(object ctd)
|
public FdsReminderData(object ctd)
|
||||||
{
|
{
|
||||||
if (ctd is JObject jo) { _base = jo; }
|
if (ctd is JObject jo) { _base = jo; }
|
||||||
@@ -84,153 +80,4 @@ public class FdsReminderData
|
|||||||
if (_base.ContainsKey("rem")) Rem = new GenericObjectDictionary(_base["rem"]!.ToObject<Dictionary<string, object>>()!);
|
if (_base.ContainsKey("rem")) Rem = new GenericObjectDictionary(_base["rem"]!.ToObject<Dictionary<string, object>>()!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public FdsReminderData(string id, IntranetController ctrl) => RegisterReminder(id, ctrl);
|
|
||||||
|
|
||||||
// -------------------------------- PDF ------------------------------------
|
|
||||||
public Document ReminderPDF(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
if (_letter != null) return _letter;
|
|
||||||
if (ReminderRegistration == null) RegisterReminder(Id, ctrl);
|
|
||||||
var tb = new FuchsPdf.FdsTextBlocks
|
|
||||||
{
|
|
||||||
AdminRef = "",
|
|
||||||
Address = InvoiceAddress,
|
|
||||||
AdminUser = UserNameFinalized,
|
|
||||||
AdminUserEmail = UserEmailFinalized,
|
|
||||||
AdminDatumValue = DateCreated ?? DateTime.Now
|
|
||||||
};
|
|
||||||
if (!string.IsNullOrEmpty(RawCustomValues))
|
|
||||||
{
|
|
||||||
var o = new GenericObjectDictionary(RawCustomValues);
|
|
||||||
string oEmail = (string?)o.getItem("contactEmail") ?? "";
|
|
||||||
string oName = (string?)o.getItem("contactName") ?? "";
|
|
||||||
if (!string.IsNullOrEmpty(oEmail) || !string.IsNullOrEmpty(oName))
|
|
||||||
{
|
|
||||||
tb.AdminUser = oName;
|
|
||||||
tb.AdminUserEmail = oEmail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_letter = Task.Run(async () => await FuchsPdf.WriteLetter(tb, draft: IsDraft, locale: FuchsPdf.DeCulture)).Result;
|
|
||||||
_letter.Info.Title = ReminderTitle;
|
|
||||||
FuchsPdf.ApplyReminder(_letter, tb, this, draft: IsDraft);
|
|
||||||
return _letter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------- File operations -----------------------------
|
|
||||||
public async Task<byte[]> GetReminderFile(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
if (ReminderRegistration?.getItem("IsFinal", false) is true)
|
|
||||||
{
|
|
||||||
if (!ReminderRegistration.ContainsKey("hasFile") || ReminderRegistration.getItem("hasFile", false) is not true)
|
|
||||||
await StoreReminderDocumentFile(ctrl);
|
|
||||||
byte[]? ba = null;
|
|
||||||
ctrl._mfr.GetFdsDoc(ref ba, Id, "reminder");
|
|
||||||
return ba ?? Array.Empty<byte>();
|
|
||||||
}
|
|
||||||
return await RenderToPdfBytes(ctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<byte[]> StoreReminderDocumentFile(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
byte[] ba;
|
|
||||||
try { ba = await RenderToPdfBytes(ctrl); }
|
|
||||||
catch { ba = Array.Empty<byte>(); }
|
|
||||||
if (ba.Length == 0) return Array.Empty<byte>();
|
|
||||||
var pl = ctrl.StdParamlist("Id", Id);
|
|
||||||
pl.Add(new SqlParameter("@file", SqlDbType.VarBinary) { Value = ba });
|
|
||||||
bool r = await setSQLValue_async("EXECUTE [dbo].[fds__setReminderFile] @Id, @file;",
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString, pl,
|
|
||||||
Security: ctrl.DbSec, options: new FIS_SQLOptions());
|
|
||||||
return r ? ba : Array.Empty<byte>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<byte[]> RenderToPdfBytes(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
var pdfrend = new PdfDocumentRenderer() { Document = ReminderPDF(ctrl) };
|
|
||||||
pdfrend.RenderDocument();
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
pdfrend.PdfDocument.Save(ms, false);
|
|
||||||
ms.Position = 0;
|
|
||||||
return OCORE.pdf._pdf.pdfAFileContent(ms.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------- Registration -------------------------------
|
|
||||||
public void RegisterReminder(IntranetController ctrl, bool change, string remId)
|
|
||||||
{
|
|
||||||
if (Rem == null || Rem.Count == 0) return;
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pl = ctrl.StdParamlist();
|
|
||||||
pl.Add(SQL_VarChar("InvId", RawInvId));
|
|
||||||
pl.Add(SQL_Char("type", Rem["type"]?.ToString() ?? ""));
|
|
||||||
pl.Add(SQL_Float("amount", stringvalue: NewValues?.nz("amount") ?? ""));
|
|
||||||
pl.Add(SQL_Float("amount_payed", stringvalue: NewValues?.nz("amount_payed") ?? ""));
|
|
||||||
pl.Add(SQL_VarChar("SendToAddress", string.Join("\n", RawInvoiceAddress)));
|
|
||||||
pl.Add(SQL_NVarChar("SendToEmail", RawInvoiceEmail));
|
|
||||||
pl.Add(SQL_NVarChar("subject", NewValues?.getString("subject") ?? "", dbNull_IfEmpty: true));
|
|
||||||
pl.Add(SQL_NVarChar("text", NewValues?.nz("text") ?? "", dbNull_IfEmpty: true));
|
|
||||||
|
|
||||||
var sqlParts = new List<string> { "DECLARE @Id varchar(10);" };
|
|
||||||
if (!change || string.IsNullOrEmpty(remId))
|
|
||||||
sqlParts.Add("EXECUTE [dbo].[fds__createReminder] @InvId, @type, @amount, @amount_payed, @SendToAddress, @SendToEmail, @subject, @text, @authuser, @Id OUTPUT;");
|
|
||||||
else
|
|
||||||
pl.Add(SQL_VarChar("RemId", remId));
|
|
||||||
|
|
||||||
var remdset = await getSQLDataSet_async(string.Join("\n", sqlParts),
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString, pl,
|
|
||||||
Security: ctrl.DbSec,
|
|
||||||
tablenames: new[] { "rem" }, options: new FIS_SQLOptions());
|
|
||||||
if (!string.IsNullOrEmpty(remdset.Exception))
|
|
||||||
ctrl._intranet.debug_log("FdsReminderData.RegisterReminder - sql exception",
|
|
||||||
data: new { exception = remdset.Exception });
|
|
||||||
ReminderRegistration = new GenericObjectDictionary(remdset.Table("rem").FirstRow.toObjectDictionary());
|
|
||||||
}
|
|
||||||
catch (Exception ex) { ctrl._intranet.debug_log("FdsReminderData.RegisterReminder", ex: ex); }
|
|
||||||
}).Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterReminder(string id, IntranetController ctrl)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(id)) return;
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pl = ctrl.StdParamlist("Id", id);
|
|
||||||
pl.Add(SQL_Bit("@includefile", false));
|
|
||||||
var remdset = await getSQLDataSet_async(
|
|
||||||
"EXECUTE [dbo].[fds__getReminder] @Id, @includefile, @authuser;",
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString, pl,
|
|
||||||
Security: ctrl.DbSec,
|
|
||||||
tablenames: new[] { "admin", "rem" }, options: new FIS_SQLOptions());
|
|
||||||
if (!string.IsNullOrEmpty(remdset.Exception))
|
|
||||||
ctrl._intranet.debug_log("FdsReminderData.RegisterReminder(id) - sql exception",
|
|
||||||
data: new { exception = remdset.Exception });
|
|
||||||
ReminderRegistration = new GenericObjectDictionary(remdset.Table("rem").FirstRow.toObjectDictionary());
|
|
||||||
IsDraft = !(ReminderRegistration.getItem("IsFinal") is true);
|
|
||||||
}
|
|
||||||
catch (Exception ex) { ctrl._intranet.debug_log("FdsReminderData.RegisterReminder(id)", ex: ex); }
|
|
||||||
}).Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FileInfo? GetStoredFile(ref byte[]? file, string reminderId, IntranetController ctrl)
|
|
||||||
{
|
|
||||||
var sqlrw = Task.Run(async () =>
|
|
||||||
(await getSQLDataSet_async(
|
|
||||||
"SELECT TOP(1) * FROM [dbo].[fds__reminder] WHERE [Id] = @Id AND [file] is not null;",
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString,
|
|
||||||
ctrl.StdParamlist(SQL_VarChar("@Id", reminderId)),
|
|
||||||
Security: ctrl.DbSec))
|
|
||||||
.FirstTable().FirstRow.toObjectDictionary()).Result;
|
|
||||||
|
|
||||||
if (sqlrw.Count > 0 && !string.IsNullOrEmpty(sqlrw.nz("DocumentName")) && sqlrw.no("file", null!) is byte[] b)
|
|
||||||
{
|
|
||||||
file = b;
|
|
||||||
return new FileInfo(sqlrw.nz("DocumentName"));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
using Fuchs.Controllers;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using OCORE.SQL;
|
|
||||||
using static OCORE.commons;
|
|
||||||
using static OCORE.OCORE_dictionaries;
|
|
||||||
using static OCORE.SQL.sql;
|
|
||||||
|
|
||||||
namespace Fuchs.intranet;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Report processing for the Fuchs intranet — SQL-driven reports from the
|
|
||||||
/// fds__ report catalog, rendered as HTML pages, HTML fragments, or PNG charts.
|
|
||||||
/// Ported from the legacy fuchs_reports.vb (process_fdsrequest) + ocms_visualization.
|
|
||||||
/// </summary>
|
|
||||||
public static class FuchsReports
|
|
||||||
{
|
|
||||||
private const int DefaultReloadSeconds = 60 * 10;
|
|
||||||
|
|
||||||
/// <param name="ctrl">Current controller (DB, security, request, user).</param>
|
|
||||||
/// <param name="fnc">Target function (e.g. "generic", "generic_content"/"gct", "chart").</param>
|
|
||||||
/// <param name="id">Report name/id (the URL <c>code</c> segment).</param>
|
|
||||||
public static async Task<IActionResult> ProcessFdsRequest(IntranetController ctrl, string fnc, string id)
|
|
||||||
{
|
|
||||||
// Merge query string + form into a single parameter map (form wins); force @authuser.
|
|
||||||
var prms = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
foreach (var kv in ctrl.Request.Query) prms[kv.Key] = kv.Value.ToString();
|
|
||||||
if (ctrl.Request.HasFormContentType)
|
|
||||||
foreach (var kv in ctrl.Request.Form) prms[kv.Key] = kv.Value.ToString();
|
|
||||||
prms["@authuser"] = ctrl.UserAccountID;
|
|
||||||
|
|
||||||
string tgt = (string.IsNullOrEmpty(fnc)
|
|
||||||
? (prms.TryGetValue("fnc", out var f) ? f : fnc)
|
|
||||||
: fnc).Replace("gct", "generic_content");
|
|
||||||
string report = prms.TryGetValue("report", out var r) && !string.IsNullOrEmpty(r)
|
|
||||||
? r
|
|
||||||
: (!string.IsNullOrEmpty(id) ? id : "");
|
|
||||||
|
|
||||||
string templatePath = Path.Combine(AppContext.BaseDirectory, "Content", "FDS_Template.html");
|
|
||||||
|
|
||||||
// Report configuration (refresh interval + cache flag) from the catalog.
|
|
||||||
int ciRefresh = -2;
|
|
||||||
bool ciCache = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var catalog = await getSQLDatatable_async(
|
|
||||||
"EXECUTE [dbo].[fds__admin_getReportCatalog] @report_name, @authuser;",
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString,
|
|
||||||
new List<SqlParameter> { SQL_VarChar("@report_name", report), SQL_VarChar("@authuser", ctrl.UserAccountID) },
|
|
||||||
Security: ctrl.DbSec, options: new FIS_SQLOptions());
|
|
||||||
var cfg = catalog.FirstRow.toObjectDictionary();
|
|
||||||
if (cfg.TryGetValue("refresh", out var rf) && rf is not null && rf is not DBNull &&
|
|
||||||
int.TryParse(rf.ToString(), out var rfi)) ciRefresh = rfi;
|
|
||||||
if (cfg.TryGetValue("functions", out var fn) && fn is not null)
|
|
||||||
ciCache = (fn.ToString() ?? "").Split(',').Contains("cache");
|
|
||||||
}
|
|
||||||
catch (Exception cex)
|
|
||||||
{
|
|
||||||
ctrl._intranet.debug_log("FuchsReports.ProcessFdsRequest - catalog", ex: cex);
|
|
||||||
}
|
|
||||||
bool ciForce = prms.TryGetValue("cache", out var ca) && ca.ToLower() == "0";
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
switch (tgt)
|
|
||||||
{
|
|
||||||
case "generic_content":
|
|
||||||
if (string.IsNullOrEmpty(report)) return new StatusCodeResult(300);
|
|
||||||
string content = await FuchsVisualization.RenderContentAsync(
|
|
||||||
ctrl, report, FdsQueryType.generic, prms);
|
|
||||||
return new ContentResult { Content = content, ContentType = "text/html" };
|
|
||||||
|
|
||||||
case "generic":
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(report)) return new StatusCodeResult(300);
|
|
||||||
var page = await FuchsVisualization.RenderPageAsync(
|
|
||||||
ctrl, report, report, FdsQueryType.generic, prms,
|
|
||||||
FdsDestination.web, templatePath, allowcache: ciCache, forceReload: ciForce);
|
|
||||||
ApplyReload(page, prms, ciRefresh);
|
|
||||||
return new ContentResult { Content = page.ToHtml(FdsDestination.web), ContentType = "text/html" };
|
|
||||||
}
|
|
||||||
|
|
||||||
case "chart":
|
|
||||||
byte[]? png = await FuchsVisualization.RenderQueryAsChartAsync(
|
|
||||||
ctrl, report, FdsQueryType.generic, prms);
|
|
||||||
if (png is null) return new StatusCodeResult(500);
|
|
||||||
return new FileContentResult(png, "image/png")
|
|
||||||
{
|
|
||||||
FileDownloadName = $"{report.Replace(" ", "_")}_{DateTime.Now:yyyyMMdd_HHmm}.png"
|
|
||||||
};
|
|
||||||
|
|
||||||
case "xls":
|
|
||||||
return new StatusCodeResult(501); // not implemented (matches legacy NotImplementedException)
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (Enum.TryParse<FdsQueryType>(fnc, ignoreCase: true, out var qt))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(report)) return new StatusCodeResult(300);
|
|
||||||
var page = await FuchsVisualization.RenderPageAsync(
|
|
||||||
ctrl, report, report, qt, prms, FdsDestination.web, templatePath);
|
|
||||||
ApplyReload(page, prms, -2);
|
|
||||||
return new ContentResult { Content = page.ToHtml(FdsDestination.web), ContentType = "text/html" };
|
|
||||||
}
|
|
||||||
return new StatusCodeResult(300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ctrl._intranet.debug_log("FuchsReports.ProcessFdsRequest",
|
|
||||||
ex: ex, data: new { fnc, id, report, tgt });
|
|
||||||
return new StatusCodeResult(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ApplyReload(FuchsHtmlPage page, IDictionary<string, string> prms, int ciRefresh)
|
|
||||||
{
|
|
||||||
if (prms.TryGetValue("reload", out var rl) && int.TryParse(rl, out var rs)) page.ReloadSeconds = rs;
|
|
||||||
else if (ciRefresh > -2) page.ReloadSeconds = ciRefresh;
|
|
||||||
else if (DefaultReloadSeconds > 0) page.ReloadSeconds = DefaultReloadSeconds;
|
|
||||||
|
|
||||||
if (page.QueryDuration > 180 && page.ReloadSeconds is > 0 and < 3600) page.ReloadSeconds = 1200;
|
|
||||||
else if (page.QueryDuration > 60 && page.ReloadSeconds is > 0 and < 1200) page.ReloadSeconds = 1200;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,11 @@ using System.Data;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Fuchs.Controllers;
|
|
||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OCORE.GenericCharts;
|
using OCORE.GenericCharts;
|
||||||
|
using OCORE.security;
|
||||||
using OCORE.SQL;
|
using OCORE.SQL;
|
||||||
using static OCORE.commons;
|
using static OCORE.commons;
|
||||||
using static OCORE.SQL.sql;
|
using static OCORE.SQL.sql;
|
||||||
@@ -177,11 +177,11 @@ internal sealed class ManagedCache
|
|||||||
public static class FuchsVisualization
|
public static class FuchsVisualization
|
||||||
{
|
{
|
||||||
// ── Query execution ─────────────────────────────────────────────────────
|
// ── Query execution ─────────────────────────────────────────────────────
|
||||||
private static string ConnStr(IntranetController ctrl) => ctrl._intranet.Intranet__SQLConnectionString;
|
|
||||||
|
|
||||||
/// <summary>Runs a report (fds__r_ / fds__xls_) and returns its admin table (ADT) + data tables.</summary>
|
/// <summary>Runs a report (fds__r_ / fds__xls_) and returns its admin table (ADT) + data tables.</summary>
|
||||||
public static async Task<(DataTable? adt, List<DataTable> dt)> GetQuery(
|
public static async Task<(DataTable? adt, List<DataTable> dt)> GetQuery(
|
||||||
IntranetController ctrl, string query, IDictionary<string, string> prms)
|
string connStr, DatabaseSecurity dbSec, string userAccountId,
|
||||||
|
string query, IDictionary<string, string> prms)
|
||||||
{
|
{
|
||||||
var dtList = new List<DataTable>();
|
var dtList = new List<DataTable>();
|
||||||
DataTable? adt = null;
|
DataTable? adt = null;
|
||||||
@@ -192,10 +192,10 @@ public static class FuchsVisualization
|
|||||||
|
|
||||||
var reportinfo = await getSQLDataSet_async(
|
var reportinfo = await getSQLDataSet_async(
|
||||||
"EXECUTE [dbo].[fds__admin_getReportCatalog] @report_name, @authuser;",
|
"EXECUTE [dbo].[fds__admin_getReportCatalog] @report_name, @authuser;",
|
||||||
ConnStr(ctrl),
|
connStr,
|
||||||
new List<SqlParameter> { SQL_VarChar("@report_name", query), SQL_VarChar("@authuser", ctrl.UserAccountID) },
|
new List<SqlParameter> { SQL_VarChar("@report_name", query), SQL_VarChar("@authuser", userAccountId) },
|
||||||
tablenames: new[] { "procedures", "parameter", "categories", "tags" },
|
tablenames: new[] { "procedures", "parameter", "categories", "tags" },
|
||||||
Security: ctrl.DbSec, options: new FIS_SQLOptions());
|
Security: dbSec, options: new FIS_SQLOptions());
|
||||||
|
|
||||||
if (!reportinfo.Contains("procedures") || reportinfo.Tables("procedures").Rows.Count == 0)
|
if (!reportinfo.Contains("procedures") || reportinfo.Tables("procedures").Rows.Count == 0)
|
||||||
return (adt, dtList);
|
return (adt, dtList);
|
||||||
@@ -204,8 +204,8 @@ public static class FuchsVisualization
|
|||||||
var procRow = reportinfo.Tables("procedures").Rows[0];
|
var procRow = reportinfo.Tables("procedures").Rows[0];
|
||||||
string sql = $"EXECUTE [dbo].[{procRow["name"]}] {procRow["parameter"]};";
|
string sql = $"EXECUTE [dbo].[{procRow["name"]}] {procRow["parameter"]};";
|
||||||
|
|
||||||
var dset = await getSQLDataSet_async(sql, ConnStr(ctrl), qparams,
|
var dset = await getSQLDataSet_async(sql, connStr, qparams,
|
||||||
Security: ctrl.DbSec, options: new FIS_SQLOptions());
|
Security: dbSec, options: new FIS_SQLOptions());
|
||||||
|
|
||||||
if (isXls)
|
if (isXls)
|
||||||
{
|
{
|
||||||
@@ -456,16 +456,18 @@ public static class FuchsVisualization
|
|||||||
|
|
||||||
/// <summary>Renders a report as a bare HTML fragment (destination = content).</summary>
|
/// <summary>Renders a report as a bare HTML fragment (destination = content).</summary>
|
||||||
public static async Task<string> RenderContentAsync(
|
public static async Task<string> RenderContentAsync(
|
||||||
IntranetController ctrl, string query, FdsQueryType qtype, IDictionary<string, string> prms)
|
string connStr, DatabaseSecurity dbSec, string userAccountId,
|
||||||
|
string query, FdsQueryType qtype, IDictionary<string, string> prms)
|
||||||
{
|
{
|
||||||
var (adt, dt) = await GetQuery(ctrl, query, prms);
|
var (adt, dt) = await GetQuery(connStr, dbSec, userAccountId, query, prms);
|
||||||
var page = new FuchsHtmlPage("", "");
|
var page = new FuchsHtmlPage("", "");
|
||||||
return await BuildFormHtmlAsync(adt, dt, qtype, FdsDestination.content, prms, page, query);
|
return await BuildFormHtmlAsync(adt, dt, qtype, FdsDestination.content, prms, page, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Renders a report as a full HTML page (destination = web / email), with optional caching.</summary>
|
/// <summary>Renders a report as a full HTML page (destination = web / email), with optional caching.</summary>
|
||||||
public static async Task<FuchsHtmlPage> RenderPageAsync(
|
public static async Task<FuchsHtmlPage> RenderPageAsync(
|
||||||
IntranetController ctrl, string uniquename, string query, FdsQueryType qtype,
|
string connStr, DatabaseSecurity dbSec, string userAccountId,
|
||||||
|
string uniquename, string query, FdsQueryType qtype,
|
||||||
IDictionary<string, string> prms, FdsDestination dest, string templatePath,
|
IDictionary<string, string> prms, FdsDestination dest, string templatePath,
|
||||||
bool allowcache = false, bool forceReload = false, string title = "")
|
bool allowcache = false, bool forceReload = false, string title = "")
|
||||||
{
|
{
|
||||||
@@ -495,7 +497,7 @@ public static class FuchsVisualization
|
|||||||
if (string.IsNullOrEmpty(cached))
|
if (string.IsNullOrEmpty(cached))
|
||||||
{
|
{
|
||||||
var start = DateTime.Now;
|
var start = DateTime.Now;
|
||||||
var (adt, dt) = await GetQuery(ctrl, query, prms);
|
var (adt, dt) = await GetQuery(connStr, dbSec, userAccountId, query, prms);
|
||||||
page.QueryDuration = (int)DateTime.Now.Subtract(start).TotalSeconds;
|
page.QueryDuration = (int)DateTime.Now.Subtract(start).TotalSeconds;
|
||||||
|
|
||||||
// Title from the admin table when available
|
// Title from the admin table when available
|
||||||
@@ -538,9 +540,10 @@ public static class FuchsVisualization
|
|||||||
|
|
||||||
/// <summary>Renders a report query directly as a PNG chart.</summary>
|
/// <summary>Renders a report query directly as a PNG chart.</summary>
|
||||||
public static async Task<byte[]?> RenderQueryAsChartAsync(
|
public static async Task<byte[]?> RenderQueryAsChartAsync(
|
||||||
IntranetController ctrl, string query, FdsQueryType qtype, IDictionary<string, string> prms)
|
string connStr, DatabaseSecurity dbSec, string userAccountId,
|
||||||
|
string query, FdsQueryType qtype, IDictionary<string, string> prms)
|
||||||
{
|
{
|
||||||
var (adt, dt) = await GetQuery(ctrl, query, prms);
|
var (adt, dt) = await GetQuery(connStr, dbSec, userAccountId, query, prms);
|
||||||
if (adt == null || dt.Count == 0) return null;
|
if (adt == null || dt.Count == 0) return null;
|
||||||
|
|
||||||
var cs = GetChartSettings(adt.Rows[0], dt[0]);
|
var cs = GetChartSettings(adt.Rows[0], dt[0]);
|
||||||
|
|||||||
@@ -1,190 +0,0 @@
|
|||||||
using Fuchs.Controllers;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using OCORE.SQL;
|
|
||||||
using static OCORE.commons;
|
|
||||||
using static OCORE.SQL.sql;
|
|
||||||
using static OCORE.web.mvc_helper_async;
|
|
||||||
|
|
||||||
namespace Fuchs.intranet;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Widget helpers for the Fuchs intranet dashboard.
|
|
||||||
/// Port of fuchs_fds_widgets.vb — SQL-driven widget cases.
|
|
||||||
/// Weather widget (wetter.com API) removed: API deprecated.
|
|
||||||
/// </summary>
|
|
||||||
public static class FuchsWidgets
|
|
||||||
{
|
|
||||||
public static async Task<IActionResult> IntranetWdg(IntranetController ctrl, string widgetId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return widgetId.ToLower() switch
|
|
||||||
{
|
|
||||||
"my" => await HandleWidgetMy(ctrl),
|
|
||||||
"one" => await HandleWidgetOne(ctrl),
|
|
||||||
_ => await HandleWidgetGeneric(ctrl, widgetId)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ctrl._intranet.debug_log("FuchsWidgets.IntranetWdg", ex, ctrl.UserAccountID,
|
|
||||||
new { widgetId });
|
|
||||||
return new StatusCodeResult(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── "my" — list of widget short-names for the current user ───────────────
|
|
||||||
private static async Task<IActionResult> HandleWidgetMy(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
var dt = await getSQLDatatable_async(
|
|
||||||
"SELECT * FROM [dbo].[fis_getONEPersonWidgets] (@authuser);",
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString,
|
|
||||||
ctrl.StdParamlist(SQL_VarChar("@account", "fis")),
|
|
||||||
Security: ctrl.DbSec);
|
|
||||||
var names = dt.DataTable.Rows
|
|
||||||
.Cast<System.Data.DataRow>()
|
|
||||||
.OrderBy(r => dt.DataTable.Columns.Contains("order") ? r.nz("order") : "")
|
|
||||||
.Select(r => r.nz("short_name"))
|
|
||||||
.ToArray();
|
|
||||||
return await JSONAsync(names);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── "one" — full widget data for a single widget ──────────────────────────
|
|
||||||
private static async Task<IActionResult> HandleWidgetOne(IntranetController ctrl)
|
|
||||||
{
|
|
||||||
string shortName = ctrl.Request.Form["short_name"].ToString() ?? "";
|
|
||||||
if (string.IsNullOrEmpty(shortName)) return new BadRequestResult();
|
|
||||||
|
|
||||||
var dt = await getSQLDatatable_async(
|
|
||||||
"SELECT * FROM [dbo].[fis_getONEPersonWidgets] (@authuser) WHERE [short_name] = @shortname;",
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString,
|
|
||||||
ctrl.StdParamlist(
|
|
||||||
SQL_VarChar("@shortname", shortName),
|
|
||||||
SQL_VarChar("@account", "fis")),
|
|
||||||
Security: ctrl.DbSec);
|
|
||||||
|
|
||||||
if (dt.Count != 1) return new StatusCodeResult(404);
|
|
||||||
var wdg = dt.FirstRow.toObjectDictionary();
|
|
||||||
return await BuildWidgetResponse(ctrl, shortName, wdg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Generic widget by id ──────────────────────────────────────────────────
|
|
||||||
private static async Task<IActionResult> HandleWidgetGeneric(IntranetController ctrl, string widgetId)
|
|
||||||
{
|
|
||||||
var pl = ctrl.StdParamlist(SQL_VarChar("@widget", widgetId, dbNull_IfEmpty: true));
|
|
||||||
var dset = await getSQLDataSet_async(
|
|
||||||
"EXECUTE [dbo].[fds__getWidget] @widget, @authuser;",
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString, pl,
|
|
||||||
tablenames: new[] { "admin", "data" },
|
|
||||||
Security: ctrl.DbSec);
|
|
||||||
return await JSONAsync(new
|
|
||||||
{
|
|
||||||
admin = dset.Table("admin").FirstRow.toObjectDictionary(),
|
|
||||||
data = dset.Tables("data").toArrayofObjectDictionaries()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Widget renderer dispatcher ────────────────────────────────────────────
|
|
||||||
private static async Task<IActionResult> BuildWidgetResponse(
|
|
||||||
IntranetController ctrl, string shortName, Dictionary<string, object?> wdg)
|
|
||||||
{
|
|
||||||
string dbType = (wdg.nz("type", "") ?? "").ToLower();
|
|
||||||
string sql = wdg.nz("sql", "") ?? "";
|
|
||||||
var ropts = ParseRenderingOptions(wdg.nz("rendering_options", "") ?? "");
|
|
||||||
string name = wdg.nz("name", "") ?? "";
|
|
||||||
string descr = wdg.nz("description", "") ?? "";
|
|
||||||
|
|
||||||
object widgetData;
|
|
||||||
|
|
||||||
switch (dbType)
|
|
||||||
{
|
|
||||||
case "sql_table":
|
|
||||||
{
|
|
||||||
var dt = await getSQLDatatable_async(sql,
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString,
|
|
||||||
ctrl.StdParamlist(), Security: ctrl.DbSec);
|
|
||||||
widgetData = new
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
description = descr,
|
|
||||||
type = "table",
|
|
||||||
rendering_options = ropts,
|
|
||||||
columns = dt.DataTable.Columns
|
|
||||||
.Cast<System.Data.DataColumn>()
|
|
||||||
.Select(c => c.ColumnName)
|
|
||||||
.ToArray(),
|
|
||||||
data = dt.DataTable.Rows
|
|
||||||
.Cast<System.Data.DataRow>()
|
|
||||||
.Select(r => r.toObjectDictionary())
|
|
||||||
.ToArray()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "sql_indicator":
|
|
||||||
{
|
|
||||||
var dt = await getSQLDatatable_async(sql,
|
|
||||||
ctrl._intranet.Intranet__SQLConnectionString,
|
|
||||||
ctrl.StdParamlist(), Security: ctrl.DbSec);
|
|
||||||
var firstRow = dt.DataTable.Rows.Count > 0
|
|
||||||
? dt.DataTable.Rows[0].toObjectDictionary()
|
|
||||||
: new Dictionary<string, object?>();
|
|
||||||
widgetData = new
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
description = descr,
|
|
||||||
type = "ind",
|
|
||||||
rendering_options = ropts,
|
|
||||||
data = new
|
|
||||||
{
|
|
||||||
status = firstRow.nz("status", "") ?? "",
|
|
||||||
value = firstRow.nz("value", "") ?? "",
|
|
||||||
label = firstRow.nz("label", "") ?? ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "html":
|
|
||||||
widgetData = new
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
description = descr,
|
|
||||||
type = "html",
|
|
||||||
rendering_options = ropts,
|
|
||||||
html = wdg.nz("html", "") ?? ""
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Pass through with normalised rendering_options
|
|
||||||
widgetData = new
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
description = descr,
|
|
||||||
type = dbType,
|
|
||||||
rendering_options = ropts,
|
|
||||||
html = wdg.nz("html", "") ?? "",
|
|
||||||
url = wdg.nz("url", "") ?? "",
|
|
||||||
image = wdg.nz("image", "") ?? "",
|
|
||||||
data = (object)new
|
|
||||||
{
|
|
||||||
status = wdg.nz("status", "") ?? "",
|
|
||||||
value = wdg.nz("value", "") ?? "",
|
|
||||||
label = wdg.nz("label", "") ?? ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap under short_name key so JS can do response[wi]
|
|
||||||
return await JSONAsync(new Dictionary<string, object> { [shortName] = widgetData });
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string[] ParseRenderingOptions(string raw) =>
|
|
||||||
string.IsNullOrWhiteSpace(raw)
|
|
||||||
? Array.Empty<string>()
|
|
||||||
: raw.Split(new[] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(s => s.Trim())
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user