feat: Implement mfr truncation fix and enhance invoice handling
Playwright Tests / test (push) Canceled after 0s

- Added a new SQL script to address the "String or binary data would be truncated" error in mfr sync by widening the Note fields in service objects and companies to NVARCHAR(MAX).
- Updated the table types and associated stored procedures to accommodate the changes, ensuring compatibility with existing data.
- Queued a re-sync for previously rejected rows due to truncation issues.
- Created application logins for fuchs_fds and fuchs_web with placeholder passwords for initial setup.
- Introduced functions for invoice backdating and invoice date retrieval to streamline invoice processing.
- Developed a password generation function with customizable parameters for length and character types.
- Added a random number generation function to support password creation.
This commit is contained in:
Stefan
2026-09-30 16:56:08 +02:00
parent 7357f60120
commit c741535aec
79 changed files with 2739 additions and 143 deletions
-1
View File
@@ -32,7 +32,6 @@ public class FdsReminderData
internal string RawCustomValues => NewValues?.nz("CustomValues").Trim() ?? "";
// -- Computed props from registration -------------------------------------
public DateTime? DateCreated => ReminderRegistration?.getString("DateCreated") is { Length: > 0 } d ? DateTime.Parse(d) : null;
public string ReminderType => ReminderRegistration?.getString("type").Substr(0, 1) ?? "";
public string UserNameFinalized => ReminderRegistration?.getString("UserNameFinalized") ?? "";
public string UserEmailFinalized => ReminderRegistration?.getString("UserEmailFinalized") ?? "";
+86
View File
@@ -0,0 +1,86 @@
using static OCORE.OCORE_dictionaries;
namespace Fuchs.intranet;
/// <summary>Result of a <c>fds__setInvoiceFinal</c> call as seen by <c>req/sconf</c>.</summary>
public enum InvoiceFinalizeOutcome
{
/// <summary>The draft was finalized (one row returned).</summary>
Finalized,
/// <summary>SQL error 60001: no longer allowed to backdate; nothing was finalized.</summary>
BackdateNotAllowed,
/// <summary>No error but no row: the invoice is already final/sent (or unknown); nothing was changed.</summary>
AlreadyFinal,
/// <summary>Any other SQL error (authorization 60000, constraint, timeout ...).</summary>
Error
}
/// <summary>
/// The invoice date ("Rechnungsdatum") printed on the PDF and carried as the e-invoice issue date
/// (BT-2). One source for both so they can never disagree (ADR 0032):
/// <c>DateFinalized</c> (the finalisation — or, for a backdated invoice, the backdated — date),
/// falling back to <c>DateCreated</c> for drafts. Both columns are written with <c>GETUTCDATE()</c>,
/// so the value is converted to the German calendar day.
/// </summary>
public static class InvoiceDates
{
private static readonly TimeZoneInfo German = ResolveGermanZone();
public static DateOnly IssueDate(GenericObjectDictionary? registration, DateTime? utcNow = null)
{
DateTime? stored = registration?.getDate_null("DateFinalized") ?? registration?.getDate_null("DateCreated");
DateTime utc = stored ?? utcNow ?? DateTime.UtcNow;
return DateOnly.FromDateTime(ToGermanTime(utc));
}
/// <summary>Parses the ISO date returned by <c>fds__fn_invoiceBackdate</c>; null when empty/invalid.</summary>
public static DateOnly? ParseBackdate(string? iso)
=> DateOnly.TryParseExact((iso ?? "").Trim(), "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None, out var d) ? d : null;
/// <summary>True when the finalise request asked for backdating (checkbox value "1"/"true").</summary>
public static bool IsBackdateRequested(string? formValue)
=> (formValue ?? "").Trim().ToLowerInvariant() is "1" or "true" or "on";
/// <summary>SQL error number of <c>THROW 60001, 'backdating not allowed'</c> in <c>fds__setInvoiceFinal</c>.</summary>
public const int BackdateNotAllowedErrorNumber = 60001;
/// <summary>
/// Classifies what <c>fds__setInvoiceFinal</c> did, from the OCORE result: an exception (with the SQL
/// error number), or the number of rows the guarded <c>UPDATE ... WHERE isFinal = 0 AND isSent = 0</c>
/// returned. Only the dedicated 60001 means "backdating no longer allowed"; a 0-row result means the
/// invoice was already final/sent (e.g. a double submit) — reporting that as a backdate rejection
/// would invite the user to create a duplicate.
/// </summary>
public static InvoiceFinalizeOutcome ClassifyFinalizeResult(string? sqlException, int? sqlErrorNumber, int rowCount)
{
if (!string.IsNullOrEmpty(sqlException))
return sqlErrorNumber == BackdateNotAllowedErrorNumber
? InvoiceFinalizeOutcome.BackdateNotAllowed
: InvoiceFinalizeOutcome.Error;
return rowCount > 0 ? InvoiceFinalizeOutcome.Finalized : InvoiceFinalizeOutcome.AlreadyFinal;
}
/// <summary>Tag value for the <c>fuchs.invoices.backdated</c> counter's <c>outcome</c> dimension.</summary>
public static string OutcomeTag(InvoiceFinalizeOutcome outcome) => outcome switch
{
InvoiceFinalizeOutcome.Finalized => "ok",
InvoiceFinalizeOutcome.BackdateNotAllowed => "rejected",
InvoiceFinalizeOutcome.AlreadyFinal => "already_final",
_ => "error"
};
internal static DateTime ToGermanTime(DateTime utc)
=> TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(utc, DateTimeKind.Utc), German);
private static TimeZoneInfo ResolveGermanZone()
{
foreach (var id in new[] { "Europe/Berlin", "W. Europe Standard Time" })
{
try { return TimeZoneInfo.FindSystemTimeZoneById(id); }
catch (TimeZoneNotFoundException) { }
catch (InvalidTimeZoneException) { }
}
return TimeZoneInfo.Local;
}
}