-- ============================================= -- One-off data backfill — run once per environment AFTER deploying the schema that carries -- reference_InvId through fds__prepInvoice / the editor and fixes the allocation MERGE in -- fds__setInvoiceFinal (2026-09-30). -- -- Until then fds__invoice_items.reference_InvId was never written for 'IntInvoiceAllocation' lines -- ("Anrechnung der Abschlagsrechnung R2025-0244 …"), so -- * fds__setInvoiceFinal's allocation MERGE failed with 515 (NULL into fds__invoice_details.InvId) -- for every Schlussrechnung — logged, invoice finalized anyway, allocation never recorded; -- * fds__invoice_details.AllocatedTo_InvId stayed NULL for every Abschlagsrechnung, so -- fds__prepInvoice would offer an already credited Abschlagsrechnung again. -- This script -- 1. restores reference_InvId from the invoice number in the line text (only unique matches), and -- 2. records AllocatedTo_InvId for each Abschlagsrechnung that is credited on a finalized, -- non-cancelled invoice (earliest such invoice wins; nothing already allocated is overwritten). -- Default is a dry run (@apply = 0) that only reports; set @apply = 1 to write. -- Idempotent: only rows with an empty reference / allocation are touched. -- ============================================= SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @apply bit = 0; -- 1 = write changes IF OBJECT_ID('tempdb..#alloc') IS NOT NULL DROP TABLE #alloc; WITH lines AS ( SELECT it.[Id], it.[InvId], [num] = SUBSTRING(it.[Text], PATINDEX('%R[0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]%', it.[Text]), 10) FROM [dbo].[fds__invoice_items] AS it WHERE it.[Type] = 'IntInvoiceAllocation' AND ISNULL(it.[reference_InvId], '') = '' AND PATINDEX('%R[0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]%', it.[Text]) > 0 ), matched AS ( SELECT l.[Id], l.[InvId], l.[num], [RefInvId] = MIN(i.[Id]), [cand] = COUNT(i.[Id]) FROM lines AS l LEFT JOIN [dbo].[fds__invoices] AS i ON i.[InvoiceId] = l.[num] AND i.[InvoiceType] = 'i' GROUP BY l.[Id], l.[InvId], l.[num] ) SELECT [ItemId] = [Id], [TgtInvId] = [InvId], [num], [RefInvId], [cand] INTO #alloc FROM matched; SELECT [lines_without_reference] = COUNT(*), [unique_match] = SUM(IIF([cand] = 1, 1, 0)), [no_match] = SUM(IIF([cand] = 0, 1, 0)), [ambiguous] = SUM(IIF([cand] > 1, 1, 0)) FROM #alloc; -- Abschlagsrechnungen credited on more than one finalized, non-cancelled invoice (review manually) SELECT a.[num], a.[RefInvId], [credited_on] = t.[InvoiceId], t.[DateFinalized], t.[InvoiceType] FROM #alloc AS a JOIN [dbo].[fds__invoices] AS t ON t.[Id] = a.[TgtInvId] AND t.[isFinal] = 1 AND ISNULL(t.[IsCanceled], 0) = 0 WHERE a.[cand] = 1 AND a.[RefInvId] IN (SELECT a2.[RefInvId] FROM #alloc AS a2 JOIN [dbo].[fds__invoices] AS t2 ON t2.[Id] = a2.[TgtInvId] AND t2.[isFinal] = 1 AND ISNULL(t2.[IsCanceled], 0) = 0 WHERE a2.[cand] = 1 GROUP BY a2.[RefInvId] HAVING COUNT(DISTINCT a2.[TgtInvId]) > 1) ORDER BY a.[num], t.[DateFinalized]; IF @apply = 1 BEGIN BEGIN TRANSACTION; UPDATE it SET it.[reference_InvId] = a.[RefInvId] FROM [dbo].[fds__invoice_items] AS it JOIN #alloc AS a ON a.[ItemId] = it.[Id] AND a.[cand] = 1 WHERE ISNULL(it.[reference_InvId], '') = ''; PRINT CONCAT('invoice item references restored: ', @@ROWCOUNT); WITH firstTarget AS ( SELECT a.[RefInvId], a.[TgtInvId], [rn] = ROW_NUMBER() OVER (PARTITION BY a.[RefInvId] ORDER BY t.[DateFinalized], t.[Id]) FROM #alloc AS a JOIN [dbo].[fds__invoices] AS t ON t.[Id] = a.[TgtInvId] AND t.[isFinal] = 1 AND ISNULL(t.[IsCanceled], 0) = 0 WHERE a.[cand] = 1 ) MERGE [dbo].[fds__invoice_details] AS TARGET USING (SELECT [RefInvId], [TgtInvId] FROM firstTarget WHERE [rn] = 1) AS src ON TARGET.[InvId] = src.[RefInvId] WHEN MATCHED AND ISNULL(TARGET.[AllocatedTo_InvId], '') = '' THEN UPDATE SET [AllocatedTo_InvId] = src.[TgtInvId] WHEN NOT MATCHED BY TARGET THEN INSERT ([InvId], [AllocatedTo_InvId]) VALUES (src.[RefInvId], src.[TgtInvId]); PRINT CONCAT('Abschlagsrechnung allocations recorded: ', @@ROWCOUNT); COMMIT TRANSACTION; END ELSE PRINT 'Dry run only — set @apply = 1 to write.'; DROP TABLE #alloc;