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
@@ -12,9 +12,13 @@ CREATE FUNCTION [dbo].[fds__fn_IntermediateIsAllocatedToOther]
RETURNS bit
AS
BEGIN
DECLARE @IsA bit = CASE WHEN EXISTS (SELECT * FROM [dbo].[fds__invoices] as intermediates JOIN [dbo].[fds__invoice_details] as id on intermediates.[Id] = id.[InvId] and id.[AllocatedTo_InvId] is not null
-- An allocation only counts while the crediting invoice is alive: once that invoice is cancelled
-- (storno) its credit line is void and the Abschlagsrechnung must be offered again (ADR 0034).
DECLARE @IsA bit = CASE WHEN EXISTS (SELECT 1 FROM [dbo].[fds__invoices] as intermediates JOIN [dbo].[fds__invoice_details] as id on intermediates.[Id] = id.[InvId] and id.[AllocatedTo_InvId] is not null
JOIN [dbo].[fds__invoices] as non_intermediates ON id.[AllocatedTo_InvId] = non_intermediates.[Id] and id.[AllocatedTo_InvId] is not null
WHERE intermediates.[Id] = @Intermediate_InvID and (@Target_InvID is null or non_intermediates.[Id] <> @Target_InvID))
WHERE intermediates.[Id] = @Intermediate_InvID
and ISNULL(non_intermediates.[IsCanceled], 0) = 0
and (@Target_InvID is null or non_intermediates.[Id] <> @Target_InvID))
THEN 1 ELSE 0 END;
RETURN @isA;
@@ -0,0 +1,33 @@
-- =============================================
-- Description: Invoice backdating (ADR 0032). Returns the date an invoice may be backdated to
-- (the last working day Mon-Fri of the previous month, Europe/Berlin), or NULL when
-- backdating is not allowed:
-- * @authuser needs fds_inv > 2, and
-- * no invoice may have been finalized after that date yet (invoice numbers are
-- issued in date order; DateFinalized is stored in UTC, hence the 2h margin).
-- Single source for fds__setInvoiceFinal (@backdate = 1) and the preview dialog.
-- The check is only race-free inside fds__setInvoiceFinal, which serializes all
-- finalizations with a transaction-owned app lock; the preview call is advisory.
-- =============================================
CREATE FUNCTION [dbo].[fds__fn_invoiceBackdate]
(
@authuser varchar(25)
)
RETURNS date
AS
BEGIN
IF [dbo].[fis_getModuleAuth]('fds_inv', @authuser) <= 2
RETURN NULL;
DECLARE @today date = CAST(SYSDATETIMEOFFSET() AT TIME ZONE 'W. Europe Standard Time' AS date);
DECLARE @eom date = EOMONTH(@today, -1);
-- DATEFIRST-independent weekday: 0 = Monday ... 5 = Saturday, 6 = Sunday
DECLARE @bd date = DATEADD(day, CASE DATEDIFF(day, '19000101', @eom) % 7 WHEN 5 THEN -1 WHEN 6 THEN -2 ELSE 0 END, @eom);
IF EXISTS (SELECT 0 FROM [dbo].[fds__invoices]
WHERE [DateFinalized] IS NOT NULL
AND [DateFinalized] >= DATEADD(HOUR, -2, CAST(DATEADD(DAY, 1, @bd) AS datetime)))
RETURN NULL;
RETURN @bd;
END
@@ -0,0 +1,18 @@
-- =============================================
-- Description: The invoice date ("Rechnungsdatum", ADR 0032) as a German calendar day:
-- DateFinalized (the finalisation - or, when backdated, the backdated - moment),
-- falling back to DateCreated for drafts. Both columns are stored in UTC.
-- SQL twin of Fuchs/code/InvoiceDates.IssueDate. Every list, report, export and
-- reminder that shows or groups by an invoice date uses this function (ADR 0035).
-- Also used for reminders (fds__reminder has the same DateFinalized/DateCreated pair).
-- =============================================
CREATE FUNCTION [dbo].[fds__fn_invoiceDate]
(
@DateFinalized datetime,
@DateCreated datetime
)
RETURNS date
AS
BEGIN
RETURN CAST(ISNULL(@DateFinalized, @DateCreated) AT TIME ZONE 'UTC' AT TIME ZONE 'W. Europe Standard Time' AS date);
END
@@ -20,11 +20,11 @@ BEGIN
WITH inv as (
SELECT
[Dateofcreation] = i.[DateFinalized]
[Dateofcreation] = [dbo].[fds__fn_invoiceDate](i.[DateFinalized], i.[DateCreated])
,i.[InvoiceBalance]
,[InvoiceBalanceNetto] = i.[InvoiceBalance_net]
FROM [dbo].[fds__invoices] as i
WHERE i.[isFinal] = 1 AND [dbo].[date_monthfirst](i.[DateFinalized]) = @thismonthfirst
WHERE i.[isFinal] = 1 AND [dbo].[date_monthfirst]([dbo].[fds__fn_invoiceDate](i.[DateFinalized], i.[DateCreated])) = @thismonthfirst
)
SELECT TOP(1) @ret = FORMAT( SUM( ISNULL(inv.[InvoiceBalanceNetto],0) ) * 0.001, '#,0 k€', 'de') FROM inv;
END
@@ -14,7 +14,7 @@ BEGIN
DECLARE @groups TABLE([usergroup_id] varchar(5));
INSERT INTO @groups SELECT * from STRING_SPLIT(@usergroup_id,',');
DECLARE @isMember bit = CASE WHEN EXISTS (SELECT * FROM [dbo].[fis_usergroups] as g
DECLARE @isMember bit = CASE WHEN EXISTS (SELECT 1 FROM [dbo].[fis_usergroups] as g
JOIN @groups as groupstotest On groupstotest.[usergroup_id] = g.[usergroup_id]
JOIN [dbo].[fis_usergroups_members] as gm on g.[usergroup_id] = gm.[usergroup_id] and gm.[DateDeleted] is null AND g.[DateDeleted] is null
JOIN [dbo].[fis_useraccounts] as u on gm.[useraccount_id] = u.[useraccount_id]
@@ -0,0 +1,88 @@
CREATE FUNCTION [dbo].[ocms_fn_generatePassword] (
@length int
, @specialChars bit = 1
, @nums bit = 1
, @lowerCase bit = 1
)
RETURNS varchar(255)
AS
BEGIN
DECLARE @randInt int;
DECLARE @NewCharacter varchar(1);
DECLARE @NewPassword TABLE([typ] char(1), [char] char(1), [order] int);
SET @length = CASE WHEN @length > 255 THEN 255 ELSE @length END;
DECLARE @cnt int = 0, @position as int;
--random characters
WHILE (@cnt < @length)
BEGIN
SELECT @randInt = [dbo].[ocms_fn_randbetween](33,122)
IF (CASE WHEN (@randInt>=33 AND @randInt<=47) AND @specialChars = 1 THEN 1
WHEN (@randInt>=48 AND @randInt<=57) AND @nums = 1 THEN 1
WHEN (@randInt>=58 AND @randInt<=64) AND @specialChars = 1 THEN 1
WHEN (@randInt>=65 AND @randInt<=90) THEN 1
WHEN (@randInt>=91 AND @randInt<=93) AND @specialChars = 1 THEN 1
WHEN (@randInt>=97 AND @randInt<=122) AND @lowerCase = 1 THEN 1
ELSE 0
END) = 1
BEGIN
SET @cnt = @cnt + 1;
INSERT INTO @NewPassword
VALUES (CASE WHEN (@randInt>=33 AND @randInt<=47) THEN 's'
WHEN (@randInt>=48 AND @randInt<=57) THEN 'n'
WHEN (@randInt>=58 AND @randInt<=64) THEN 's'
WHEN (@randInt>=65 AND @randInt<=90) THEN 'u'
WHEN (@randInt>=91 AND @randInt<=93) THEN 's'
WHEN (@randInt>=97 AND @randInt<=122) THEN 'l'
END,
CHAR(@randInt), @cnt);
END
END
DECLARE @min_n int = CASE WHEN @length > 4 AND @length < 11 THEN 1 ELSE FLOOR(@length * 0.1) END;
DECLARE @min_s int = FLOOR(@length * 0.1);
DECLARE @min_l int = FLOOR(@length * 0.4);
DECLARE @min_u int = FLOOR(@length * 0.4);
WHILE (SELECT COUNT(*) FROM @NewPassword WHERE [typ] = 'n') < @min_n AND @length > 0 AND @nums = 1
BEGIN
SET @position = [dbo].[ocms_fn_randbetween](1,@length);
UPDATE @NewPassword SET [char] = CHAR([dbo].[ocms_fn_randbetween](48,57)), [typ] = 'n' WHERE [order] = @position;
END
WHILE (SELECT COUNT(*) FROM @NewPassword WHERE [typ] = 'l') < @min_l AND @length > 0 AND @lowerCase = 1
BEGIN
SET @position = [dbo].[ocms_fn_randbetween](1,@length);
UPDATE @NewPassword SET [char] = CHAR([dbo].[ocms_fn_randbetween](97,122)), [typ] = 'l' WHERE [order] = @position;
END
WHILE (SELECT COUNT(*) FROM @NewPassword WHERE [typ] = 'u') < @min_n AND @length > 0
BEGIN
SET @position = [dbo].[ocms_fn_randbetween](1,@length);
UPDATE @NewPassword SET [char] = CHAR([dbo].[ocms_fn_randbetween](65,90)), [typ] = 'u' WHERE [order] = @position;
END
WHILE (SELECT COUNT(*) FROM @NewPassword WHERE [typ] = 's') < @min_s AND @length > 0 AND @specialChars = 1
BEGIN
SET @position = [dbo].[ocms_fn_randbetween](1,@length);
DECLARE @nc char(1), @nt char(1);
select @randInt=[dbo].[ocms_fn_randbetween](33,122)
UPDATE @NewPassword SET [char] = CHAR(@randint), [typ] = 's' WHERE [order] = @position AND (CASE WHEN (@randInt>=33 AND @randInt<=47) THEN 's'
WHEN (@randInt>=48 AND @randInt<=57) THEN 'n'
WHEN (@randInt>=58 AND @randInt<=64) THEN 's'
WHEN (@randInt>=65 AND @randInt<=90) THEN 'u'
WHEN (@randInt>=91 AND @randInt<=93) THEN 's'
WHEN (@randInt>=97 AND @randInt<=122) THEN 'l'
END) = 's';
END
RETURN (Cast((SELECT [CHAR] as [text()] FROM @NewPassword FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)') as varchar(255)));
END;
@@ -0,0 +1,8 @@
CREATE FUNCTION [dbo].[ocms_fn_randbetween](@bottom integer, @top integer)
RETURNS integer
AS
BEGIN
RETURN (select cast(round((@top-@bottom) * [rn] +@bottom,0) as integer) from [dbo].[ocms_fn_rand])
END