Add structured logging to IntranetController actions

Extensive structured logging was added throughout IntranetController and all invoice/account handlers to improve traceability and debugging. Logging now covers action entry/exit, error conditions, and key parameters (user IDs, invoice IDs, etc.). Handlers log warnings for missing/invalid input and info/debug for significant events. Minor refactoring extracts form values for better logging. The jQuery `rwText` plugin was hardened against null input. Updated minified JS, font assets, and OCORE submodule. No functional changes to `tools.js`.
This commit is contained in:
2026-06-04 14:21:14 +02:00
parent 8f8d462045
commit dbe6cd8653
12 changed files with 328 additions and 86 deletions
@@ -17,34 +17,51 @@ public partial class IntranetController
{
private async Task<IActionResult> Do_Process_Invoices(string fn, string id, string code)
{
_logger.LogDebug("Do_Process_Invoices called: fn={Fn} id={Id} code={Code} user={User}", fn, id, code, UserAccountID);
switch (id.ToLower())
{
case "auth":
_logger.LogDebug("Invoice auth check for user {User}", UserAccountID);
return await JSONAsync(new { manage = 1 });
case "setpyd":
if (!HasForm("id")) return BadRequest400();
return await setSQLValue_async(
if (!HasForm("id")) { _logger.LogWarning("setpyd: missing form field 'id', user={User}", UserAccountID); return BadRequest400(); }
{
var invoiceId = Form("id");
_logger.LogInformation("setpyd: marking invoice {InvoiceId} as paid, user={User}", invoiceId, UserAccountID);
var ok = await setSQLValue_async(
"EXECUTE [dbo].[fds__setInvoicePayed] @Id, @authuser;",
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_VarChar("@Id", Form("id"))),
Security: DbSec, options: SqlOpt(fn, id, code))
? Ok() : StatusCode(500);
StdParamlist(SQL_VarChar("@Id", invoiceId)),
Security: DbSec, options: SqlOpt(fn, id, code));
if (!ok) _logger.LogError("setpyd: SQL failed for invoice {InvoiceId}, user={User}", invoiceId, UserAccountID);
return ok ? Ok() : StatusCode(500);
}
case "setupd":
if (!HasForm("id")) return BadRequest400();
return await setSQLValue_async(
if (!HasForm("id")) { _logger.LogWarning("setupd: missing form field 'id', user={User}", UserAccountID); return BadRequest400(); }
{
var invoiceId = Form("id");
_logger.LogInformation("setupd: marking invoice {InvoiceId} as unpaid, user={User}", invoiceId, UserAccountID);
var ok = await setSQLValue_async(
"EXECUTE [dbo].[fds__setInvoiceUNPayed] @Id, @authuser;",
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_VarChar("@Id", Form("id"))),
Security: DbSec, options: SqlOpt(fn, id, code))
? Ok() : StatusCode(500);
StdParamlist(SQL_VarChar("@Id", invoiceId)),
Security: DbSec, options: SqlOpt(fn, id, code));
if (!ok) _logger.LogError("setupd: SQL failed for invoice {InvoiceId}, user={User}", invoiceId, UserAccountID);
return ok ? Ok() : StatusCode(500);
}
case "setvat":
if (!float.TryParse(Form("val").Replace("%", "").Replace(",", ".").Trim(),
NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out float vatVal))
return BadRequest400();
{
_logger.LogWarning("setvat: invalid VAT value '{Val}', user={User}", Form("val"), UserAccountID);
return BadRequest400();
}
{
_logger.LogInformation("setvat: setting VAT {Vat} on report {ReportId}, user={User}", vatVal, Form("id"), UserAccountID);
var pl = StdParamlist(
SQL_BigInt("@id", Form("id")),
SQL_VarChar("@entitytype", "report"),
@@ -53,42 +70,90 @@ public partial class IntranetController
string sqlEx = ""; int? sqlCode = null;
setSQLValue("EXECUTE [dbo].[fds__setReportVAT] @id, @entitytype, @vat, @authuser;",
_intranet.Intranet_SqlCon(), ref sqlEx, ref sqlCode, pl, Security: DbSec);
if (!string.IsNullOrEmpty(sqlEx))
_logger.LogError("setvat: SQL error for report {ReportId}: {SqlError}, user={User}", Form("id"), sqlEx, UserAccountID);
return string.IsNullOrEmpty(sqlEx) ? Ok() : StatusCode(500, new { error = sqlEx });
}
case "sis":
if (!HasForm("id")) return BadRequest400();
if (!HasForm("id")) { _logger.LogWarning("sis: missing form field 'id', user={User}", UserAccountID); return BadRequest400(); }
{
var pl = StdParamlist(SQL_VarChar("@Id", Form("id")), SQL_Bit("@auto", false));
var invoiceId = Form("id");
_logger.LogInformation("sis: marking invoice {InvoiceId} as sent, user={User}", invoiceId, UserAccountID);
var pl = StdParamlist(SQL_VarChar("@Id", invoiceId), SQL_Bit("@auto", false));
var dt2 = await getSQLDataSet_async(
"EXECUTE [dbo].[fds__setInvoiceSent] @Id, @auto, @authuser;",
_intranet.Intranet__SQLConnectionString, pl,
Security: DbSec, options: SqlOpt(fn, id, code));
if (!string.IsNullOrEmpty(dt2.Exception))
_logger.LogError("sis: SQL error for invoice {InvoiceId}: {SqlError}, user={User}", invoiceId, dt2.Exception, UserAccountID);
return string.IsNullOrEmpty(dt2.Exception) ? Ok() : StatusCode(500);
}
case "pget": return await HandleInvoicePget(fn, id, code);
case "get": return await HandleInvoiceGet(fn, id, code);
case "icget": return await HandleInvoiceIcGet(fn, id, code);
case "pget":
_logger.LogDebug("pget: invoice PDF get, user={User}", UserAccountID);
return await HandleInvoicePget(fn, id, code);
case "get":
_logger.LogDebug("get: invoice get, user={User}", UserAccountID);
return await HandleInvoiceGet(fn, id, code);
case "icget":
_logger.LogDebug("icget: invoice IC get, user={User}", UserAccountID);
return await HandleInvoiceIcGet(fn, id, code);
case "storno":
case "credit": return await HandleInvoiceStornoCredit(fn, id, code);
case "invl": return await HandleInvoiceList(fn, id, code);
case "rqi": return await HandleInvoiceRequestItems(fn, id, code);
case "pyi": return await HandleInvoicePayments(fn, id, code);
case "datev": return await HandleDatev(fn, id, code);
case "rdoc": return await HandleReportDoc(fn, id, code, Form("id"));
case "rdocn": return await HandleReportDocByName(fn, id, code);
case "datevzip": return await HandleDatevZip(fn, id, code);
case "getrem": return await HandleGetReminder(fn, id, code);
case "credit":
_logger.LogInformation("{Action}: invoice storno/credit, user={User}", id, UserAccountID);
return await HandleInvoiceStornoCredit(fn, id, code);
case "invl":
_logger.LogDebug("invl: invoice list, user={User}", UserAccountID);
return await HandleInvoiceList(fn, id, code);
case "rqi":
_logger.LogDebug("rqi: invoice request items, user={User}", UserAccountID);
return await HandleInvoiceRequestItems(fn, id, code);
case "pyi":
_logger.LogDebug("pyi: invoice payments, user={User}", UserAccountID);
return await HandleInvoicePayments(fn, id, code);
case "datev":
_logger.LogDebug("datev: DATEV export, user={User}", UserAccountID);
return await HandleDatev(fn, id, code);
case "rdoc":
_logger.LogDebug("rdoc: report document get id={DocId}, user={User}", Form("id"), UserAccountID);
return await HandleReportDoc(fn, id, code, Form("id"));
case "rdocn":
_logger.LogDebug("rdocn: report document get by name, user={User}", UserAccountID);
return await HandleReportDocByName(fn, id, code);
case "datevzip":
_logger.LogDebug("datevzip: DATEV ZIP export, user={User}", UserAccountID);
return await HandleDatevZip(fn, id, code);
case "getrem":
_logger.LogDebug("getrem: get reminder for invoice, user={User}", UserAccountID);
return await HandleGetReminder(fn, id, code);
case "mfrrel":
if (!HasForm("id") || !long.TryParse(Form("id"), out long relId)) return BadRequest400();
if (!HasForm("id") || !long.TryParse(Form("id"), out long relId))
{
_logger.LogWarning("mfrrel: missing or invalid form field 'id', user={User}", UserAccountID);
return BadRequest400();
}
_logger.LogInformation("mfrrel: resetting MFR relation for invoice {InvoiceId}, user={User}", relId, UserAccountID);
using (var mfr = new fds.FdsMfrClient())
await mfr.Update__entitytable(EntityTypes.Invoice,
fds.FdsMfr.UpdateNeed.Reset, new[] { relId });
return Ok();
default: return Ok();
default:
_logger.LogWarning("Do_Process_Invoices: unhandled action id={Id}, user={User}", id, UserAccountID);
return Ok();
}
}
}
+131 -33
View File
@@ -18,11 +18,16 @@ public partial class IntranetController
{
private async Task<IActionResult> HandleInvoicePget(string fn, string id, string code)
{
if (!HasForm("id")) return BadRequest400();
if (!long.TryParse(Form("id"), out long tgtid)) return BadRequest400();
if (!HasForm("id")) { _logger.LogWarning("HandleInvoicePget: missing 'id' form field user={User}", 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);
using (var mfr = new fds.FdsMfrClient())
{
_logger.LogDebug("HandleInvoicePget resetting invoice entity tgtid={TgtId}", tgtid);
await mfr.Update__entitytable(EntityTypes.Invoice,
fds.FdsMfr.UpdateNeed.Reset, new[] { tgtid });
}
var dt = await getSQLDatatable_async(
"SELECT * FROM [dbo].[fds__getInvoiceTreeIds](@srqid);",
@@ -30,6 +35,7 @@ public partial class IntranetController
StdParamlist(SQL_BigInt("@srqid", tgtid)),
Security: DbSec, options: SqlOpt(fn, id, code));
_logger.LogDebug("HandleInvoicePget tree query returned {Count} rows for tgtid={TgtId}", dt.Count, tgtid);
if (dt.Count > 0)
{
var invIds = new List<long>();
@@ -43,11 +49,14 @@ public partial class IntranetController
case "servicerequest": if (iid > 0 && !srqIds.Contains(iid)) srqIds.Add(iid); break;
}
}
_logger.LogDebug("HandleInvoicePget resetting {InvCount} invoices and {SrqCount} service requests", invIds.Count, srqIds.Count);
using var mfr2 = new fds.FdsMfrClient();
foreach (var iid in invIds)
await mfr2.Update__entitytable(EntityTypes.Invoice, fds.FdsMfr.UpdateNeed.Reset, new[] { iid });
foreach (var iid in srqIds)
await mfr2.Update__entitytable(EntityTypes.ServiceRequest, fds.FdsMfr.UpdateNeed.Reset, new[] { iid });
_logger.LogInformation("HandleInvoicePget reset complete for tgtid={TgtId} invoices={InvCount} serviceRequests={SrqCount} user={User}",
tgtid, invIds.Count, srqIds.Count, UserAccountID);
}
return Ok();
}
@@ -56,35 +65,48 @@ public partial class IntranetController
{
try
{
if (!HasForm("id")) return BadRequest400();
if (!HasForm("id")) { _logger.LogWarning("HandleInvoiceGet: missing 'id' form field user={User}", UserAccountID); return BadRequest400(); }
string invoiceId = Form("id");
_logger.LogDebug("HandleInvoiceGet invoiceId={InvoiceId} user={User}", invoiceId, UserAccountID);
var sqldset = await getSQLDataSet_async(
"EXECUTE [dbo].[fds__getInvoice] @Id, @authuser;",
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_VarChar("@Id", Form("id"))),
StdParamlist(SQL_VarChar("@Id", invoiceId)),
tablenames: new[] { "admin", "inv", "req", "itm" },
Security: DbSec, options: SqlOpt(fn, id, code));
var ldic = BuildInvoiceRequestList(sqldset);
var adminDic = sqldset.Table("admin").FirstRow.toObjectDictionary();
var invDic = sqldset.Table("inv").FirstRow.toObjectDictionary();
if (invDic.nz("InvoiceOptions", "").Split(',').Contains("§13b"))
bool has13b = invDic.nz("InvoiceOptions", "").Split(',').Contains("§13b");
if (has13b)
adminDic["p13b"] = true;
_logger.LogDebug("HandleInvoiceGet invoiceId={InvoiceId} requestCount={ReqCount} has13b={Has13b} user={User}",
invoiceId, ldic.Count, has13b, UserAccountID);
return await JSONAsync(new { admin = adminDic, inv = invDic, req = ldic });
}
catch { return StatusCode(500); }
catch (Exception ex)
{
_logger.LogError(ex, "HandleInvoiceGet failed for id={InvoiceId} user={User}", Form("id"), UserAccountID);
return StatusCode(500);
}
}
private async Task<IActionResult> HandleInvoiceIcGet(string fn, string id, string code)
{
if (!HasForm("id")) return BadRequest400();
if (!HasForm("id")) { _logger.LogWarning("HandleInvoiceIcGet: missing 'id' form field user={User}", UserAccountID); return BadRequest400(); }
string invoiceId = Form("id");
_logger.LogDebug("HandleInvoiceIcGet (storno/recreate prep) invoiceId={InvoiceId} user={User}", invoiceId, UserAccountID);
var sqldset = await getSQLDataSet_async(
"EXECUTE [dbo].[fds__prepStorno_recreate] @InvId, @authuser;",
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_VarChar("@InvId", Form("id"))),
StdParamlist(SQL_VarChar("@InvId", invoiceId)),
tablenames: new[] { "admin", "requests", "items", "steps", "companies", "locations" },
Security: DbSec, options: SqlOpt(fn, id, code));
var ldic = BuildRequestItemList(sqldset);
_logger.LogDebug("HandleInvoiceIcGet invoiceId={InvoiceId} requestCount={ReqCount} user={User}",
invoiceId, ldic.Count, UserAccountID);
return await JSONAsync(new
{
admin = sqldset.Table("admin").FirstRow.toObjectDictionary(),
@@ -96,34 +118,47 @@ public partial class IntranetController
private async Task<IActionResult> HandleInvoiceStornoCredit(string fn, string id, string code)
{
if (!HasForm("id", "mode")) return BadRequest400();
string sqlcmd = Form("mode") switch
if (!HasForm("id", "mode")) { _logger.LogWarning("HandleInvoiceStornoCredit: missing required form fields user={User}", UserAccountID); return BadRequest400(); }
string invoiceId = Form("id");
string mode = Form("mode");
_logger.LogDebug("HandleInvoiceStornoCredit invoiceId={InvoiceId} mode={Mode} user={User}", invoiceId, mode, UserAccountID);
string sqlcmd = mode switch
{
"credit" => "EXECUTE [dbo].[fds__createCredit_simple] @Id, @authuser;",
"simple" => "EXECUTE [dbo].[fds__createStorno_simple] @Id, @authuser;",
"copy" => "EXECUTE [dbo].[fds__createStorno_copy] @Id, @authuser;",
_ => ""
};
if (string.IsNullOrEmpty(sqlcmd)) return StatusCode(500, new { error = "function not allowed" });
if (string.IsNullOrEmpty(sqlcmd))
{
_logger.LogWarning("HandleInvoiceStornoCredit: unknown mode={Mode} invoiceId={InvoiceId} user={User}", mode, invoiceId, UserAccountID);
return StatusCode(500, new { error = "function not allowed" });
}
_logger.LogInformation("HandleInvoiceStornoCredit executing mode={Mode} invoiceId={InvoiceId} user={User}", mode, invoiceId, UserAccountID);
var sqldset = await getSQLDataSet_async(sqlcmd,
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_VarChar("@Id", Form("id"))),
StdParamlist(SQL_VarChar("@Id", invoiceId)),
tablenames: new[] { "admin", "inv", "req", "itm" },
Security: DbSec, options: SqlOpt(fn, id, code));
var reqList = BuildInvoiceRequestList(sqldset);
_logger.LogDebug("HandleInvoiceStornoCredit complete mode={Mode} invoiceId={InvoiceId} requestCount={ReqCount} user={User}",
mode, invoiceId, reqList.Count, UserAccountID);
return await JSONAsync(new
{
admin = sqldset.Table("admin").FirstRow.toObjectDictionary(),
inv = sqldset.Table("inv").FirstRow.toObjectDictionary(),
req = BuildInvoiceRequestList(sqldset)
req = reqList
});
}
private async Task<IActionResult> HandleInvoiceList(string fn, string id, string code)
{
if (!HasForm("mode")) return BadRequest400();
if (!HasForm("mode")) { _logger.LogWarning("HandleInvoiceList: missing 'mode' form field user={User}", UserAccountID); return BadRequest400(); }
string mode = Form("mode").ToLower();
_logger.LogDebug("HandleInvoiceList mode={Mode} tgt={Tgt} user={User}", mode, Form("tgt"), UserAccountID);
if (mode == "s" && Form("tgt").Contains(':'))
{
_logger.LogDebug("HandleInvoiceList using search path mode={Mode} search={Search} user={User}", mode, Form("tgt"), UserAccountID);
var pl = StdParamlist(
SQL_Date("@tgtdate", DBNull.Value),
SQL_VarChar("@mode", Form("mode").ne("m")),
@@ -134,6 +169,8 @@ public partial class IntranetController
_intranet.Intranet__SQLConnectionString, pl,
tablenames: new[] { "admin", "invoices" },
Security: DbSec, options: SqlOpt(fn, id, code));
_logger.LogDebug("HandleInvoiceList search returned {Count} invoices user={User}",
dset.Tables("invoices").Rows.Count, UserAccountID);
return await JSONAsync(new
{
admin = dset.Table("admin").FirstRow.toObjectDictionary(),
@@ -142,17 +179,25 @@ public partial class IntranetController
}
if (!DateTime.TryParseExact(Form("tgt"), "yy-MM-dd",
CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var tgtdate))
return BadRequest400();
{
_logger.LogWarning("HandleInvoiceList: invalid date format tgt='{Tgt}' user={User}", Form("tgt"), UserAccountID);
return BadRequest400();
}
{
string includes = Form("includes").ne(Form("all") == "true" ? "all" : "");
_logger.LogDebug("HandleInvoiceList date-based tgtdate={TgtDate} mode={Mode} includes={Includes} user={User}",
tgtdate.ToString("yy-MM-dd"), mode, includes, UserAccountID);
var pl = StdParamlist(
SQL_Date("@tgtdate", tgtdate),
SQL_VarChar("@mode", Form("mode").ne("m")),
SQL_VarChar("@includes", Form("includes").ne(Form("all") == "true" ? "all" : "")));
SQL_VarChar("@includes", includes));
var dset = await getSQLDataSet_async(
"EXECUTE [dbo].[fds__getInvoices_list_vario] @tgtdate, @mode, @includes, @authuser;",
_intranet.Intranet__SQLConnectionString, pl,
tablenames: new[] { "admin", "invoices" },
Security: DbSec, options: SqlOpt(fn, id, code));
_logger.LogDebug("HandleInvoiceList date-based returned {Count} invoices user={User}",
dset.Tables("invoices").Rows.Count, UserAccountID);
return await JSONAsync(new
{
admin = dset.Table("admin").FirstRow.toObjectDictionary(),
@@ -163,11 +208,13 @@ public partial class IntranetController
private async Task<IActionResult> HandleInvoiceRequestItems(string fn, string id, string code)
{
if (!HasForm("id")) return BadRequest400();
if (!HasForm("id")) { _logger.LogWarning("HandleInvoiceRequestItems: missing 'id' form field user={User}", UserAccountID); return BadRequest400(); }
string invoiceId = Form("id");
_logger.LogDebug("HandleInvoiceRequestItems invoiceId={InvoiceId} user={User}", invoiceId, UserAccountID);
var sqldt = await getSQLDataSet_async(
"EXECUTE [dbo].[fds__getInvRequestItems] @invoiceid, @authuser;",
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_VarChar("@invoiceid", Form("id"))),
StdParamlist(SQL_VarChar("@invoiceid", invoiceId)),
tablenames: new[] { "requests", "items" },
Security: DbSec, options: SqlOpt(fn, id, code));
var ldic = new List<Dictionary<string, object?>>();
@@ -180,18 +227,25 @@ public partial class IntranetController
.Select(r => r.toObjectDictionary()).ToList();
ldic.Add(sdic!);
}
_logger.LogDebug("HandleInvoiceRequestItems invoiceId={InvoiceId} requestCount={ReqCount} user={User}",
invoiceId, ldic.Count, UserAccountID);
return await JSONAsync(new { requests = ldic });
}
private async Task<IActionResult> HandleInvoicePayments(string fn, string id, string code)
{
if (!HasForm("id")) return BadRequest400();
if (!HasForm("id")) { _logger.LogWarning("HandleInvoicePayments: missing 'id' form field user={User}", UserAccountID); return BadRequest400(); }
string invoiceId = Form("id");
_logger.LogDebug("HandleInvoicePayments invoiceId={InvoiceId} user={User}", invoiceId, UserAccountID);
var sqldt = await getSQLDataSet_async(
"EXECUTE [dbo].[fds__getInvPayments] @invoiceid, @authuser;",
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_VarChar("@invoiceid", Form("id"))),
StdParamlist(SQL_VarChar("@invoiceid", invoiceId)),
tablenames: new[] { "items" },
Security: DbSec, options: SqlOpt(fn, id, code));
int paymentCount = sqldt.Tables("items").Rows.Count;
_logger.LogDebug("HandleInvoicePayments invoiceId={InvoiceId} paymentCount={PaymentCount} user={User}",
invoiceId, paymentCount, UserAccountID);
return await JSONAsync(new { payments = sqldt.Tables("items").toArrayofObjectDictionaries() });
}
@@ -199,13 +253,20 @@ public partial class IntranetController
{
if (!DateTime.TryParseExact(Form("tgt"), "yy-MM-dd",
CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var tgtdate))
{
_logger.LogWarning("HandleDatev: invalid date format tgt='{Tgt}' user={User}", Form("tgt"), UserAccountID);
return BadRequest400();
}
string mode = Form("mode").ne("m");
_logger.LogDebug("HandleDatev tgtdate={TgtDate} mode={Mode} user={User}", tgtdate.ToString("yy-MM-dd"), mode, UserAccountID);
var dset = await getSQLDataSet_async(
"EXECUTE [dbo].[fds__getDatevExports] @tgtdate, @mode, @authuser;",
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_Date("@tgtdate", tgtdate), SQL_VarChar("@mode", Form("mode").ne("m"))),
StdParamlist(SQL_Date("@tgtdate", tgtdate), SQL_VarChar("@mode", mode)),
tablenames: new[] { "files", "invoices", "debits" },
Security: DbSec, options: SqlOpt(fn, id, code));
_logger.LogDebug("HandleDatev tgtdate={TgtDate} files={FileCount} invoices={InvCount} user={User}",
tgtdate.ToString("yy-MM-dd"), dset.Tables("files").Rows.Count, dset.Tables("invoices").Rows.Count, UserAccountID);
return await JSONAsync(new
{
files = dset.Tables("files").toArrayofObjectDictionaries(),
@@ -215,9 +276,16 @@ public partial class IntranetController
private async Task<IActionResult> HandleReportDoc(string fn, string id, string code, string reportid)
{
_logger.LogDebug("HandleReportDoc reportid={ReportId} typ={Typ} user={User}", reportid, Form("typ"), UserAccountID);
byte[]? content = null;
var file = _mfr.GetReportDoc(ref content, reportid);
if (file == null) return StatusCode(404, new { error = "Dokument wurde nicht gefunden" });
if (file == null)
{
_logger.LogWarning("HandleReportDoc: document not found reportid={ReportId} user={User}", reportid, UserAccountID);
return StatusCode(404, new { error = "Dokument wurde nicht gefunden" });
}
_logger.LogDebug("HandleReportDoc found reportid={ReportId} fileName={FileName} mimeType={MimeType} size={Size} user={User}",
reportid, file.Name, file.MimeType(), content?.Length ?? 0, UserAccountID);
return Form("typ") != "img"
? await FileContentResultAsync(content!, file.MimeType(), file.Name)
: await JSONAsync(new { id = reportid, img = await BuildPdfImageArray(content!) });
@@ -225,46 +293,76 @@ public partial class IntranetController
private async Task<IActionResult> HandleReportDocByName(string fn, string id, string code)
{
if (!HasForm("name")) return BadRequest400();
if (!HasForm("name")) { _logger.LogWarning("HandleReportDocByName: missing 'name' form field user={User}", UserAccountID); return BadRequest400(); }
string nme = Form("name").LeftToFirst("(").Trim();
if (string.IsNullOrEmpty(nme)) return StatusCode(404);
_logger.LogDebug("HandleReportDocByName name='{Name}' user={User}", nme, UserAccountID);
if (string.IsNullOrEmpty(nme))
{
_logger.LogWarning("HandleReportDocByName: empty name after trim user={User}", UserAccountID);
return StatusCode(404);
}
var so = await getSQLValue_async(
"SELECT [dbo].[fds__fn_InvoiceIdByName](@nme);",
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_VarChar("@nme", nme)),
Security: DbSec, options: SqlOpt(fn, id, code));
string reportid = so.Result?.ToString() ?? "";
return string.IsNullOrEmpty(reportid)
? StatusCode(404)
: await HandleReportDoc(fn, id, code, reportid);
if (string.IsNullOrEmpty(reportid))
{
_logger.LogWarning("HandleReportDocByName: no invoice found for name='{Name}' user={User}", nme, UserAccountID);
return StatusCode(404);
}
_logger.LogDebug("HandleReportDocByName resolved name='{Name}' to reportid={ReportId} user={User}", nme, reportid, UserAccountID);
return await HandleReportDoc(fn, id, code, reportid);
}
private async Task<IActionResult> HandleDatevZip(string fn, string id, string code)
{
if (!DateTime.TryParseExact(Form("tgt"), "yy-MM-dd",
CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var tgtdate))
{
_logger.LogWarning("HandleDatevZip: invalid date format tgt='{Tgt}' user={User}", Form("tgt"), UserAccountID);
return BadRequest400();
}
string mode = Form("mode").ne("m");
bool includeFiles = Form("files", "1") != "0";
_logger.LogDebug("HandleDatevZip tgtdate={TgtDate} mode={Mode} includeFiles={IncludeFiles} user={User}",
tgtdate.ToString("yy-MM-dd"), mode, includeFiles, UserAccountID);
Stream? ms = new MemoryStream();
var file = _mfr.GetDatevZip(ref ms, tgtdate,
mode: Form("mode").ne("m"),
mode: mode,
authUser: UserAccountID,
includeFiles: Form("files", "1") != "0");
if (file == null) return BadRequest400();
includeFiles: includeFiles);
if (file == null)
{
_logger.LogWarning("HandleDatevZip: zip generation returned null for tgtdate={TgtDate} mode={Mode} user={User}",
tgtdate.ToString("yy-MM-dd"), mode, UserAccountID);
return BadRequest400();
}
_logger.LogInformation("HandleDatevZip sending file='{FileName}' tgtdate={TgtDate} user={User}",
file.Name, tgtdate.ToString("yy-MM-dd"), UserAccountID);
ms!.Position = 0;
return await FileStreamResultAsync(ms, file.MimeType(), file.Name);
}
private async Task<IActionResult> HandleGetReminder(string fn, string id, string code)
{
if (!HasForm("id")) return BadRequest400();
if (!HasForm("id")) { _logger.LogWarning("HandleGetReminder: missing 'id' form field user={User}", UserAccountID); return BadRequest400(); }
string invoiceId = Form("id");
string includeDrafts = Form("drafts");
_logger.LogDebug("HandleGetReminder invoiceId={InvoiceId} includeDrafts={IncludeDrafts} user={User}",
invoiceId, includeDrafts, UserAccountID);
var pl = StdParamlist(
SQL_VarChar("@InvId", Form("id")),
SQL_Bit("@include_drafts", Form("drafts")));
SQL_VarChar("@InvId", invoiceId),
SQL_Bit("@include_drafts", includeDrafts));
var sqldt = await getSQLDataSet_async(
"EXECUTE [dbo].[fds__getInvoiceReminder] @InvId, @include_drafts, @authuser;",
_intranet.Intranet__SQLConnectionString, pl,
tablenames: new[] { "reminder" },
Security: DbSec, options: SqlOpt(fn, id, code));
int reminderCount = sqldt.Table("reminder").DataTable.Rows.Count;
_logger.LogDebug("HandleGetReminder invoiceId={InvoiceId} reminderCount={ReminderCount} user={User}",
invoiceId, reminderCount, UserAccountID);
return await JSONAsync(sqldt.Table("reminder").DataTable.toArrayofObjectDictionaries());
}
+95 -15
View File
@@ -76,23 +76,27 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
// ── Index (GET /) ─────────────────────────────────────────────────────────
[AllowAnonymous]
public IActionResult Index(string? fn, string? id, string? code) =>
public IActionResult Index([FromRoute] string? fn, [FromRoute] string? id, [FromRoute] string? code) =>
View("intranet");
// ── Do (POST+GET /do/{fn}/{id}/{code}) ─────────────────────────────────
[AllowAnonymous]
public async Task<IActionResult> Do(string? fn, string? id, string? code)
public async Task<IActionResult> Do([FromRoute] string? fn, [FromRoute] string? id, [FromRoute] string? code)
{
fn = (fn ?? "").ToLower();
id ??= "";
code ??= "";
bool isGet = HttpContext.Request.Method.Equals("GET", StringComparison.OrdinalIgnoreCase);
_logger.LogDebug("Do dispatching {Fn}/{Id}/{Code} [{Method}] user={User}",
fn, id, code, HttpContext.Request.Method, UserAccountID);
if (!UserIdent.IsAuthenticated && !(new string[] { "login","logout" }).Contains(fn.ToLower()) && !_allowedNonAuth.Contains(fn.ToLower()))
{
if (!_allowedGet.Contains(fn.ToLower()) && !_allowedGet.Contains($"{fn.ToLower()}|{id.ToLower()}"))
{
_logger.LogInformation($"rejected function on do {fn}");
_logger.LogWarning("Rejected unauthenticated request for fn={Fn} id={Id} ip={IP}",
fn, id, HttpContext.Connection.RemoteIpAddress);
return Unauthorized401();
}
}
@@ -121,12 +125,16 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
"logout" => await HandleLogout(),
_ => null
};
if (result == null)
_logger.LogWarning("No handler matched fn={Fn}", fn);
else
_logger.LogDebug("Do completed fn={Fn}/{Id} result={ResultType}", fn, id, result.GetType().Name);
return result ?? Ok();
}
catch (Exception ex)
{
_intranet.debug_log("IntranetController.Do", ex, UserAccountID,
data: new { fn, id, code });
_logger.LogError(ex, "Unhandled exception in Do fn={Fn} id={Id} code={Code} user={User}",
fn, id, code, UserAccountID);
return ServerError();
}
}
@@ -134,8 +142,14 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
// ── Auth helper ───────────────────────────────────────────────────────────
private async Task<IActionResult> HandleAuth(string fn, string id, string code)
{
if (!Request.Form.ContainsKey("module")) return BadRequest400();
if (!Request.Form.ContainsKey("module"))
{
_logger.LogWarning("HandleAuth called without 'module' form field by user={User}", UserAccountID);
return BadRequest400();
}
string module = Request.Form["module"].ToString();
_logger.LogDebug("HandleAuth module={Module} array={Array} user={User}",
module, Request.Form["array"].ToString(), UserAccountID);
if (Request.Form["array"] == "1")
{
var dt = await getSQLDatatable_async(
@@ -143,6 +157,8 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
_intranet.Intranet__SQLConnectionString,
StdParamlist(SQL_VarChar("@module", module)),
Security: DbSec, options: SqlOpt(fn, id, code));
_logger.LogDebug("HandleAuth returned {Count} module auth entries for user={User}",
dt.DataTable.Rows.Count, UserAccountID);
return await JSONAsync(dt.DataTable.ToDictionary(KeyColumn: "module", ValueColumn: "auth"));
}
else
@@ -152,6 +168,8 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
_intranet.Intranet__SQLConnectionString, -1,
StdParamlist(SQL_VarChar("@module", module)),
Security: DbSec, options: SqlOpt(fn, id, code));
_logger.LogDebug("HandleAuth module={Module} auth={Auth} user={User}",
module, val.Result, UserAccountID);
return await JSONAsync(new { auth = val.Result });
}
}
@@ -162,13 +180,13 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
{
string email = Request.Form["userinfo"].ToString();
string password = Request.Form["userpass"].ToString();
_logger.LogDebug("HandleLogin attempt for email={Email} ip={IP}",
email, HttpContext.Connection.RemoteIpAddress);
var row = await _intranet.AuthenticateAsync(email, password);
if (row == null)
{
_logger.LogWarning("Login failed for '{Email}' from {IP}",
_logger.LogWarning("Login failed for email={Email} ip={IP}",
email, HttpContext.Connection.RemoteIpAddress);
_intranet.debug_log("HandleLogin: failed",
data: new { email, ip = HttpContext.Connection.RemoteIpAddress?.ToString() });
return Unauthorized401();
}
@@ -179,6 +197,8 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
var identity = FuchsUserIdentity.BuildIdentity(userId, userEmail, auth, Fuchs_intranet.AuthScheme);
var principal = new System.Security.Claims.ClaimsPrincipal(identity);
await HttpContext.SignInAsync(Fuchs_intranet.AuthScheme, principal);
_logger.LogInformation("Login succeeded for userId={UserId} email={Email} authorization={Auth} ip={IP}",
userId, userEmail, auth, HttpContext.Connection.RemoteIpAddress);
return await JSONAsync(new
{
login = userEmail,
@@ -192,7 +212,10 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
private async Task<IActionResult> HandleLogout()
{
_logger.LogInformation("Logout user={User} ip={IP}",
UserAccountID, HttpContext.Connection.RemoteIpAddress);
await HttpContext.SignOutAsync(Fuchs_intranet.AuthScheme);
_logger.LogDebug("Logout sign-out complete for user={User}", UserAccountID);
return Ok();
}
@@ -201,16 +224,27 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
{
string? lastname = Request.Form["lastname"];
string? email = Request.Form["email"];
if (string.IsNullOrEmpty(lastname) || string.IsNullOrEmpty(email)) return BadRequest400();
_logger.LogDebug("HandleSendPasswordCode called for email={Email}", email);
if (string.IsNullOrEmpty(lastname) || string.IsNullOrEmpty(email))
{
_logger.LogWarning("HandleSendPasswordCode missing lastname or email");
return BadRequest400();
}
var row = await _intranet.GetUserAccountByEmailAsync(email);
if (row != null && row.nz("email").Length > 5 &&
row.nz("name").ToLower().Trim() == lastname.ToLower().Trim() &&
row.nz("mobile").Length > 5 && !Request.Host.Host.ToLower().Contains("localhost"))
{
_logger.LogInformation("Sending password code SMS to mobile for email={Email}", email);
string totp = OCORE.security.TFA.generateTotp_12h(_intranet.Intranet__TOTPsharedsecret_base);
await _comService.SendSmsAsync(row.nz("mobile"),
"Zur Bestätigung des Passwortversands auf sanitarfuchs.de, verwenden Sie bitte folgenden Code:" + totp);
_logger.LogDebug("Password code SMS sent for email={Email}", email);
}
else
{
_logger.LogDebug("HandleSendPasswordCode: no SMS sent for email={Email} (user not found, name mismatch, no mobile, or localhost)", email);
}
return Ok(); // always OK to prevent enumeration
}
@@ -220,34 +254,57 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
string? lastname = Request.Form["lastname"];
string? email = Request.Form["email"];
string? totpCode = Request.Form["code"];
_logger.LogDebug("HandleSendPassword called for email={Email}", email);
if (string.IsNullOrEmpty(lastname) || string.IsNullOrEmpty(email) || string.IsNullOrEmpty(totpCode))
return BadRequest400();
if (OCORE.security.TFA.validateTotp_12h(_intranet.Intranet__TOTPsharedsecret_base, totpCode).isVerifiedInTime)
{
_logger.LogWarning("HandleSendPassword missing required fields (lastname, email or code)");
return BadRequest400();
}
var totpResult = OCORE.security.TFA.validateTotp_12h(_intranet.Intranet__TOTPsharedsecret_base, totpCode);
if (totpResult.isVerifiedInTime)
{
_logger.LogDebug("HandleSendPassword TOTP verified for email={Email}", email);
var row = await _intranet.GetUserAccountByEmailAsync(email, includePassword: true);
if (row != null && row.nz("email").Length > 5)
{
_logger.LogInformation("Sending password email to email={Email}", email);
await _comService.SendEmailAsync("pw_" + row.nz("email"),
"sanitaerfuchs.de Intranet Passwort",
$"<p>Guten Tag {row.nz("firstname")} {row.nz("name")},<br/>Ihr Passwort: {HttpUtility.HtmlEncode(row.nz("password"))}</p>",
row.nz("email"), $"{row.nz("firstname")} {row.nz("name")}", null);
_logger.LogDebug("Password email sent for email={Email}", email);
}
else
{
_logger.LogWarning("HandleSendPassword: user not found for email={Email}", email);
}
}
else
{
_logger.LogWarning("HandleSendPassword: TOTP verification failed for email={Email}", email);
}
return Ok();
}
private async Task<IActionResult> HandleAccount(string fn, string id, string code)
{
_logger.LogDebug("HandleAccount action={Action} user={User}", id, UserAccountID);
switch (id.ToLower())
{
case "sms":
var row = await _intranet.GetUserAccountByEmailAsync(UserIdent.Email, includePassword: true);
if (row != null && row.nz("mobile").Length > 5 && !Request.Host.Host.Contains("localhost"))
{
_logger.LogInformation("Sending change-password confirmation SMS to user={User}", UserAccountID);
string totp2 = OCORE.security.TFA.generateTotp_3h(_intranet.Intranet__TOTPsharedsecret_base + "3MDR");
await _comService.SendSmsAsync(row.nz("mobile"),
"Zur Bestätigung der Passwortänderung auf sanitarfuchs.de: " + totp2);
_logger.LogDebug("Change-password SMS sent for user={User}", UserAccountID);
}
else
{
_logger.LogDebug("HandleAccount sms: no SMS sent for user={User} (no mobile or localhost)", UserAccountID);
}
return Ok();
@@ -256,9 +313,16 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
string? npwc = Request.Form["npwc"];
string? totpCode = Request.Form["code"];
if (string.IsNullOrEmpty(npw) || string.IsNullOrEmpty(npwc) || string.IsNullOrEmpty(totpCode))
{
_logger.LogWarning("HandleAccount changepassword: missing required fields for user={User}", UserAccountID);
return BadRequest400();
}
if (!OCORE.security.TFA.validateTotp_3h(_intranet.Intranet__TOTPsharedsecret_base + "3MDR", totpCode).isVerifiedInTime)
{
_logger.LogWarning("HandleAccount changepassword: TOTP verification failed for user={User}", UserAccountID);
return StatusCode(409, new { error = "sms" });
}
_logger.LogInformation("Changing password for user={User}", UserAccountID);
await setSQLValue_async(
"EXECUTE [dbo].[fis_admin_setNewPassword] @useraccount_id, @oldpassword, @newpassword, @enc_key;",
_intranet.Intranet__SQLConnectionString,
@@ -269,39 +333,55 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
SQL_VarChar("@newpassword", npw)
},
Security: DbSec, options: SqlOpt(fn, id, code));
_logger.LogDebug("Password changed successfully for user={User}", UserAccountID);
return Ok();
}
_logger.LogWarning("HandleAccount unknown action={Action} user={User}", id, UserAccountID);
return Ok();
}
private async Task<IActionResult> HandleMfr(string fn, string id, string code)
{
_logger.LogDebug("HandleMfr id={Id} code={Code} user={User} auth={Auth}",
id, code, UserAccountID, UserIdent.Authorization);
if (!string.IsNullOrEmpty(UserAccountID) && UserIdent.Authorization > 3)
{
string path = id + (!string.IsNullOrEmpty(code) ? "/" + code : HttpUtility.UrlDecode(Request.QueryString.Value ?? ""));
_logger.LogDebug("HandleMfr reading OData path={Path} user={User}", path, UserAccountID);
using var mfrRead = new fds.FdsMfrClient();
var result = await mfrRead.ReadOData(path, throwErrorIfNotOk: false);
_logger.LogDebug("HandleMfr OData read complete for path={Path} user={User}", path, UserAccountID);
return Content(JsonConvert.SerializeObject(result), "application/json");
}
_logger.LogWarning("HandleMfr access denied for user={User} authorization={Auth}",
UserAccountID, UserIdent.Authorization);
return Ok();
}
private async Task<IActionResult> HandleMfrUpdate(string fn, string id, string code)
{
var et = EntityHelper.EntityValue(Request.Form["type"].ToString());
string typeParam = Request.Form["type"].ToString();
string needParam = Request.Form["need"].ToString();
var et = EntityHelper.EntityValue(typeParam);
_logger.LogDebug("HandleMfrUpdate type={Type} need={Need} user={User}", typeParam, needParam, UserAccountID);
if (et != EntityTypes.none && string.IsNullOrEmpty(Request.Form["need"]))
{
_logger.LogInformation("MfrUpdate entity={EntityType} need=Short user={User}", et, UserAccountID);
using var mfrSingle = new fds.FdsMfrClient();
await mfrSingle.Update__entitytable(et, fds.FdsMfr.UpdateNeed.Short);
_logger.LogDebug("MfrUpdate Short completed for entity={EntityType}", et);
return Ok();
}
if (et != EntityTypes.none && !string.IsNullOrEmpty(Request.Form["need"]))
{
var need = fds.FdsMfr.UpdateNeedValue(Request.Form["need"].ToString());
var need = fds.FdsMfr.UpdateNeedValue(needParam);
_logger.LogInformation("MfrUpdate entity={EntityType} need={Need} user={User}", et, need, UserAccountID);
using var mfr = new fds.FdsMfrClient();
await mfr.Update__entitytable(et, updateNeed: need, debugDetails: false);
_logger.LogDebug("MfrUpdate completed for entity={EntityType} need={Need}", et, need);
return Ok();
}
_logger.LogWarning("HandleMfrUpdate bad request: unknown type={Type} user={User}", typeParam, UserAccountID);
return BadRequest400();
}
}
+1 -1
View File
@@ -224,7 +224,7 @@ function getMonday(d) {
$.fn.rwText = function (text, addtitle, options) {
var tgt = $(this).empty();
options = $.extend({ wrap: true }, options);
var sa = Array.isArray(text) === true ? text : (text || '').split('\n');
var sa = Array.isArray(text) === true ? text : (text == null ? '' : String(text)).split('\n');
$.each(sa, function (ti, tx) {
if ((tx || '') !== '') {
if (ti > 0) {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1317,7 +1317,7 @@ function getMonday(d) {
$.fn.rwText = function (text, addtitle, options) {
var tgt = $(this).empty();
options = $.extend({ wrap: true }, options);
var sa = Array.isArray(text) === true ? text : (text || '').split('\n');
var sa = Array.isArray(text) === true ? text : (text == null ? '' : String(text)).split('\n');
$.each(sa, function (ti, tx) {
if ((tx || '') !== '') {
if (ti > 0) {
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
Submodule OCORE updated: 758c28e934...3cf8cd1dd5