feat: implement set-based invoice payment calculations and enhance email logging configuration
Playwright Tests / test (push) Canceled after 0s
Playwright Tests / test (push) Canceled after 0s
This commit is contained in:
@@ -61,6 +61,7 @@
|
||||
<Folder Include="dbo\Tables\" />
|
||||
<Folder Include="dbo\Views\" />
|
||||
<Folder Include="Security\" />
|
||||
<Folder Include="Scripts\" />
|
||||
<Folder Include="dbo\Functions\" />
|
||||
<Folder Include="dbo\Stored Procedures\" />
|
||||
<Folder Include="dbo\User Defined Types\" />
|
||||
@@ -199,6 +200,7 @@
|
||||
<Build Include="dbo\Functions\fds__fn_invoice-srq_id.sql" />
|
||||
<Build Include="dbo\Functions\fds__fn_InvoicePaymentAmount_full.sql" />
|
||||
<Build Include="dbo\Functions\fds__fn_InvoicePaymentAmount.sql" />
|
||||
<Build Include="dbo\Functions\fds__fn_InvoicePaymentAmounts.sql" />
|
||||
<Build Include="dbo\Functions\fds__fn_InvoiceIDs_mfr.sql" />
|
||||
<Build Include="dbo\Functions\fds__fn_InvoiceIdByName.sql" />
|
||||
<Build Include="dbo\Functions\fds__fn_invoice_id.sql" />
|
||||
@@ -405,4 +407,7 @@
|
||||
<Build Include="dbo\Functions\fds_wgd_lastEmails.sql" />
|
||||
<Build Include="dbo\Functions\fds_wdg_MFRSync.sql" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Scripts\2026-09-16_fis_widgets_set_based_payment_amounts.sql" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,31 @@
|
||||
-- =============================================
|
||||
-- One-off data migration (fis_widgets rows are data, not schema — run once per environment
|
||||
-- after deploying the schema that adds [dbo].[fds__fn_InvoicePaymentAmounts] and the
|
||||
-- [PaymentAmount]/[OpenAmount] columns of [dbo].[fds__fn_unpaidInvoices]).
|
||||
--
|
||||
-- Both widgets called the scalar [fds__fn_InvoicePaymentAmount](Id) once per invoice row. That
|
||||
-- function declares a table variable, so SQL Server cannot inline it and re-runs the whole
|
||||
-- banking TVF per row (~0.5 s each): wdg_invoices_overdue_sum took ~100 s over 187 unpaid
|
||||
-- invoices and timed out the 90 s widget command; wdg_unbez_akonto took ~5 s (hidden by daycache).
|
||||
-- Set-based replacements give identical totals in < 0.5 s.
|
||||
-- Idempotent: each UPDATE only rewrites the old text and is a no-op once applied.
|
||||
-- =============================================
|
||||
SET NOCOUNT ON;
|
||||
|
||||
UPDATE [dbo].[fis_widgets]
|
||||
SET [sql] = REPLACE([sql], N'[invoicebalance] - [dbo].[fds__fn_InvoicePaymentAmount](Id)', N'[OpenAmount]')
|
||||
WHERE [short_name] = 'wdg_invoices_overdue_sum'
|
||||
AND [sql] LIKE N'%[[]dbo].[[]fds__fn_InvoicePaymentAmount](Id)%';
|
||||
PRINT CONCAT('wdg_invoices_overdue_sum rows updated: ', @@ROWCOUNT);
|
||||
|
||||
UPDATE [dbo].[fis_widgets]
|
||||
SET [sql] = REPLACE(REPLACE([sql]
|
||||
, N'[dbo].[fds__fn_InvoicePaymentAmount]([id])', N'ISNULL(pay.[Amount], 0.0)')
|
||||
, N'FROM [dbo].[fds__invoices] as i where', N'FROM [dbo].[fds__invoices] as i LEFT JOIN [dbo].[fds__fn_InvoicePaymentAmounts]() as pay ON pay.[InvId] = i.[id] where')
|
||||
WHERE [short_name] = 'wdg_unbez_akonto'
|
||||
AND [sql] LIKE N'%[[]dbo].[[]fds__fn_InvoicePaymentAmount]([[]id])%'
|
||||
AND [sql] LIKE N'%FROM [[]dbo].[[]fds__invoices] as i where%';
|
||||
PRINT CONCAT('wdg_unbez_akonto rows updated: ', @@ROWCOUNT);
|
||||
|
||||
-- Verify: nothing on the dashboard still calls the per-row scalar function.
|
||||
SELECT [short_name], [sql] FROM [dbo].[fis_widgets] WHERE [sql] LIKE N'%fds__fn_InvoicePaymentAmount](%';
|
||||
@@ -0,0 +1,23 @@
|
||||
-- =============================================
|
||||
-- Description: Set-based counterpart of the scalar [fds__fn_InvoicePaymentAmount]:
|
||||
-- returns the summed (non-storno) banking payments for EVERY invoice in one pass
|
||||
-- over [fds__fn_bankingtransactions]. Join it instead of calling the scalar
|
||||
-- function per row — the scalar one declares a table variable, so SQL Server
|
||||
-- cannot inline it and re-runs the whole banking TVF for each invoice
|
||||
-- (~0.5 s × N rows; the overdue-sum widget timed out at 90 s with 187 rows).
|
||||
-- Same semantics as the scalar version with an empty id list: all invoices,
|
||||
-- no date filter, storno credits NOT included (see *_full for those).
|
||||
-- =============================================
|
||||
CREATE FUNCTION [dbo].[fds__fn_InvoicePaymentAmounts]
|
||||
(
|
||||
)
|
||||
RETURNS TABLE
|
||||
AS
|
||||
RETURN
|
||||
(
|
||||
SELECT [InvId] = bs.[InvId]
|
||||
,[Amount] = CAST(SUM(ISNULL(bs.[Amount], 0.0)) as numeric(9,2))
|
||||
FROM [dbo].[fds__fn_bankingtransactions](NULL, NULL, NULL, NULL, DEFAULT) as bs
|
||||
WHERE bs.[InvId] IS NOT NULL
|
||||
GROUP BY bs.[InvId]
|
||||
)
|
||||
@@ -1,7 +1,9 @@
|
||||
-- =============================================
|
||||
-- Author: <Author,,Name>
|
||||
-- Create date: <Create Date,,>
|
||||
-- Description: <Description,,>
|
||||
-- Description: Finalized, not-paid, not-canceled, not-storno'd invoices with a balance > 0.
|
||||
-- [PaymentAmount] / [OpenAmount] come from one set-based pass over the banking
|
||||
-- transactions ([fds__fn_InvoicePaymentAmounts]) — use them instead of calling the
|
||||
-- scalar [fds__fn_InvoicePaymentAmount](Id) per row, which re-runs the whole
|
||||
-- banking TVF for every invoice and timed out the dashboard widgets.
|
||||
-- =============================================
|
||||
CREATE FUNCTION [dbo].[fds__fn_unpaidInvoices]
|
||||
(
|
||||
@@ -28,6 +30,10 @@ RETURN
|
||||
and InvoiceBalance > 0
|
||||
AND NOT exists (SELECT 0 FROM [dbo].[fds__invoice_details] as d where d.[StornoTo_InvId] = iv.[id])
|
||||
)
|
||||
SELECT * /*[Rechnung] = [invoiceId], [Name] = [InvoiceTitle] + CHAR(10) + ' (' + (SELECT TOP(1) c.[name] FROM [dbo].[mfr__companies] as c WHERE inv.[customerid] = c.[id]) + ')', [Betrag (net)] = FORMAT([invoicebalance], '#0.00€', 'de'), [Datum] = FORMAT([DateFinalized], 'dd.MM.yy' , 'de'), [Zahlungen] = FORMAT([dbo].[fds__fn_InvoicePaymentAmount]([id]), '#0.00€', 'de'), [order] = ROW_NUMBER() OVER (ORDER BY [DateFinalized], [DateSent]) */
|
||||
SELECT inv.*
|
||||
,[PaymentAmount] = ISNULL(pay.[Amount], 0.0)
|
||||
,[OpenAmount] = CAST(inv.[InvoiceBalance] - ISNULL(pay.[Amount], 0.0) as numeric(10,2))
|
||||
/*[Rechnung] = [invoiceId], [Name] = [InvoiceTitle] + CHAR(10) + ' (' + (SELECT TOP(1) c.[name] FROM [dbo].[mfr__companies] as c WHERE inv.[customerid] = c.[id]) + ')', [Betrag (net)] = FORMAT([invoicebalance], '#0.00€', 'de'), [Datum] = FORMAT([DateFinalized], 'dd.MM.yy' , 'de'), [Zahlungen] = FORMAT([dbo].[fds__fn_InvoicePaymentAmount]([id]), '#0.00€', 'de'), [order] = ROW_NUMBER() OVER (ORDER BY [DateFinalized], [DateSent]) */
|
||||
FROM inv
|
||||
)
|
||||
LEFT JOIN [dbo].[fds__fn_InvoicePaymentAmounts]() as pay ON pay.[InvId] = inv.[Id]
|
||||
)␍
|
||||
@@ -2,6 +2,10 @@
|
||||
returns table
|
||||
as return
|
||||
|
||||
-- [config] is NOT NULL but a logging path may have stored '' (or otherwise non-JSON text).
|
||||
-- OPENJSON/JSON_QUERY raise on that and took the whole widget down, so the JSON is normalised
|
||||
-- once (invalid -> '{}') before anything parses it; a WHERE ISJSON() alone is not guaranteed to
|
||||
-- be evaluated ahead of the CROSS APPLY.
|
||||
SELECT TOP (@count)
|
||||
[Betreff] = oj.[subject],
|
||||
[Empfänger] = (SELECT STRING_AGG(
|
||||
@@ -9,9 +13,10 @@ SELECT TOP (@count)
|
||||
THEN SUBSTRING([value], CHARINDEX('<', [value]) + 1, CHARINDEX('>', [value]) - CHARINDEX('<', [value]) - 1)
|
||||
ELSE [value]
|
||||
END, ', ')
|
||||
FROM OPENJSON(JSON_QUERY(el.[config], '$.to'))),
|
||||
[attachments] = (SELECT STRING_AGG([value], ', ') FROM OPENJSON(JSON_QUERY(el.[config], '$.attachments'))),
|
||||
FROM OPENJSON(JSON_QUERY(j.[cfg], '$.to'))),
|
||||
[attachments] = (SELECT STRING_AGG([value], ', ') FROM OPENJSON(JSON_QUERY(j.[cfg], '$.attachments'))),
|
||||
[gesendet] = FORMAT(el.DateSent, 'dd.MM.yy', 'de-DE')
|
||||
FROM [dbo].[fds__emaillog] as el
|
||||
CROSS APPLY OPENJSON(el.[config], '$') WITH ( [subject] nvarchar(255) '$.subject' ) as oj
|
||||
CROSS APPLY (SELECT [cfg] = IIF(ISJSON(el.[config]) = 1, el.[config], N'{}')) as j
|
||||
CROSS APPLY OPENJSON(j.[cfg], '$') WITH ( [subject] nvarchar(255) '$.subject' ) as oj
|
||||
order by [DateSent] desc
|
||||
Reference in New Issue
Block a user