Add Azure Blob Storage archive & email safety net

- Add AzureBlobStorageService, DocumentArchiveSyncService, and related config for secondary PDF archiving of invoices/reminders
- Add SQL procs and schema changes for archive backfill
- Update invoice/reminder services to upload PDFs to blob storage
- Add telemetry counters and unit tests for blob storage/archive logic
- Add Fuchs:Email:OverrideRecipient config and enforce dev/test email redirect in ProcessWebComService, with tests
- Improve JS date parsing (German formats), stricter JSON date detection
- Increase widget SQL timeouts, update dependencies, docs, and project files
This commit is contained in:
Stefan
2026-07-03 10:15:04 +02:00
parent 3035ef1719
commit c3395bc83c
47 changed files with 1723 additions and 101 deletions
@@ -0,0 +1,18 @@
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: Returns the stored file bytes for a single invoice, for use by
-- the Azure Blob Storage startup backfill after the row has been
-- selected via fds__getInvoiceFiles_ForBlobArchive.
-- =============================================
CREATE PROCEDURE [dbo].[fds__getInvoiceFileContent]
@Id varchar(10)
AS
BEGIN
SET NOCOUNT ON;
SELECT TOP(1) [Id], [file]
FROM [dbo].[fds__invoices]
WHERE [Id] = @Id AND @Id is not null AND [file] IS NOT NULL;
END
@@ -0,0 +1,27 @@
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: Enumerates invoices that already have a stored file, returning
-- lightweight metadata only (no file bytes) so the Azure Blob
-- Storage startup backfill (see AzureBlobStorageService /
-- DocumentArchiveSyncService) can enumerate candidates cheaply
-- before fetching each file's content individually via
-- fds__getInvoiceFileContent.
-- =============================================
CREATE PROCEDURE [dbo].[fds__getInvoiceFiles_ForBlobArchive]
AS
BEGIN
SET NOCOUNT ON;
SELECT
[Id]
,[Version]
,[InvoiceId]
,[InvoiceTitle]
,[DocumentName]
,[file_guid]
FROM [dbo].[fds__invoices]
WHERE [file] IS NOT NULL
ORDER BY [Id];
END
@@ -0,0 +1,18 @@
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: Returns the stored file bytes for a single reminder, for use by
-- the Azure Blob Storage startup backfill after the row has been
-- selected via fds__getReminderFiles_ForBlobArchive.
-- =============================================
CREATE PROCEDURE [dbo].[fds__getReminderFileContent]
@Id varchar(10)
AS
BEGIN
SET NOCOUNT ON;
SELECT TOP(1) [Id], [file]
FROM [dbo].[fds__reminder]
WHERE [Id] = @Id AND @Id is not null AND [file] IS NOT NULL;
END
@@ -0,0 +1,25 @@
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: Enumerates reminders that already have a stored file, returning
-- lightweight metadata only (no file bytes) so the Azure Blob
-- Storage startup backfill (see AzureBlobStorageService /
-- DocumentArchiveSyncService) can enumerate candidates cheaply
-- before fetching each file's content individually via
-- fds__getReminderFileContent.
-- =============================================
CREATE PROCEDURE [dbo].[fds__getReminderFiles_ForBlobArchive]
AS
BEGIN
SET NOCOUNT ON;
SELECT
[Id]
,[Version]
,[InvId]
,[DocumentName]
FROM [dbo].[fds__reminder]
WHERE [file] IS NOT NULL
ORDER BY [Id];
END
+26 -25
View File
@@ -1,29 +1,30 @@
CREATE TABLE [dbo].[fds__reminder] (
[Id] VARCHAR (10) NOT NULL,
[Version] INT CONSTRAINT [DF_fds__reminder_Version] DEFAULT ((0)) NOT NULL,
[DocumentName] VARCHAR (100) NULL,
[InvId] VARCHAR (15) NOT NULL,
[CustomerId] BIGINT NULL,
[SendToAddress] NVARCHAR (1000) NULL,
[SendToEmail] NVARCHAR (255) NULL,
[type] VARCHAR (3) NOT NULL,
[amount] NUMERIC (10, 3) NULL,
[amount_payed] NUMERIC (10, 3) NULL,
[amount_open] AS (CONVERT([numeric](10,3),isnull([amount],(0))-isnull([amount_payed],(0)))),
[subject] NVARCHAR (255) NULL,
[text] NVARCHAR (2000) NULL,
[IsSent] BIT CONSTRAINT [DF_fds__reminder_IsSent] DEFAULT ((0)) NOT NULL,
[IsFinal] AS (CONVERT([bit],case when [DateFinalized] IS NULL then (0) else (1) end)),
[CustomValues] NVARCHAR (MAX) NULL,
[DateSent] DATETIME NULL,
[UserSent] VARCHAR (25) NULL,
[DateFinalized] DATETIME NULL,
[UserFinalized] VARCHAR (25) NULL,
[DateCreated] DATETIME NOT NULL,
[UserCreated] VARCHAR (25) NOT NULL,
[DateModified] DATETIME NOT NULL,
[UserModified] VARCHAR (25) NOT NULL,
[file] VARBINARY (MAX) NULL,
[Id] VARCHAR (10) NOT NULL,
[Version] INT CONSTRAINT [DF_fds__reminder_Version] DEFAULT ((0)) NOT NULL,
[DocumentName] VARCHAR (100) NULL,
[InvId] VARCHAR (15) NOT NULL,
[CustomerId] BIGINT NULL,
[SendToAddress] NVARCHAR (1000) NULL,
[SendToEmail] NVARCHAR (255) NULL,
[type] VARCHAR (3) NOT NULL,
[amount] NUMERIC (10, 3) NULL,
[amount_payed] NUMERIC (10, 3) NULL,
[amount_open] AS (CONVERT([numeric](10,3),isnull([amount],(0))-isnull([amount_payed],(0)))),
[subject] NVARCHAR (255) NULL,
[text] NVARCHAR (2000) NULL,
[IsSent] BIT CONSTRAINT [DF_fds__reminder_IsSent] DEFAULT ((0)) NOT NULL,
[IsFinal] AS (CONVERT([bit],case when [DateFinalized] IS NULL then (0) else (1) end)),
[CustomValues] NVARCHAR (MAX) NULL,
[DateSent] DATETIME NULL,
[UserSent] VARCHAR (25) NULL,
[DateFinalized] DATETIME NULL,
[UserFinalized] VARCHAR (25) NULL,
[DateCreated] DATETIME NOT NULL,
[UserCreated] VARCHAR (25) NOT NULL,
[DateModified] DATETIME NOT NULL,
[UserModified] VARCHAR (25) NOT NULL,
[file] VARBINARY (MAX) NULL,
[file_guid] UNIQUEIDENTIFIER CONSTRAINT [DF_fds__reminder_file_guid] DEFAULT (newid()) NOT NULL,
CONSTRAINT [PK_fds__reminder] PRIMARY KEY CLUSTERED ([Id] ASC)
);