feat: Implement admin-only specialties management interface
Playwright Tests / test (push) Canceled after 0s

- Added MailerAttachmentPolicy for validating email attachments against server rules.
- Introduced fis.specialties.js for managing hidden maintenance actions via an admin interface.
- Created fis.specialties.scss for styling the specialties management UI.
- Added German localization for specialties management in fis.specialties_txt_de.js.
- Developed CSS for specialties management in fis.specialties.css.
- Implemented server-side stored procedure fds__admin_backupAndClearInvoiceFile for backing up and clearing invoice files.
- Created fds__invoice_file_backups table to store backup records of invoice files.
This commit is contained in:
Stefan
2026-09-17 21:17:23 +02:00
parent 86f6a60860
commit 249a945f5e
59 changed files with 2720 additions and 457 deletions
+2
View File
@@ -127,6 +127,7 @@
<Build Include="dbo\Tables\fds__mfr_invoicefiles.sql" />
<Build Include="dbo\Tables\fds__log.sql" />
<Build Include="dbo\Tables\fds__invoices.sql" />
<Build Include="dbo\Tables\fds__invoice_file_backups.sql" />
<Build Include="dbo\Tables\fds__invoice_servicerequests.sql" />
<Build Include="dbo\Tables\fds__invoice_items.sql" />
<Build Include="dbo\Tables\fds__invoice_details.sql" />
@@ -357,6 +358,7 @@
<Build Include="dbo\Stored Procedures\fds__admin_logdebug.sql" />
<Build Include="dbo\Stored Procedures\fds__admin_logdebug_ext.sql" />
<Build Include="dbo\Stored Procedures\fds__admin_getReportCatalog.sql" />
<Build Include="dbo\Stored Procedures\fds__admin_backupAndClearInvoiceFile.sql" />
<Build Include="dbo\Stored Procedures\_BackupAndClearInvoiceFile.sql" />
<Build Include="dbo\User Defined Types\json_data_maxU.sql" />
<Build Include="dbo\User Defined Types\hash_256.sql" />
@@ -0,0 +1,62 @@
-- =============================================
-- Author: Dr. Stefan Ott
-- Create date: 2026-09-17
-- Description: App-facing counterpart to the manual [dbo].[_BackupAndClearInvoiceFile] maintenance
-- tool (see that proc's header comment and Docs/Decisions/0025's "Alternatives
-- considered"). That proc is explicitly manual-only and leaves no application log
-- entry, so it is never called from here — this is a separate proc for the hidden
-- admin/backupclearinvoicefile action (fds_sys > 4 gate, IntranetController.Admin.cs),
-- which logs through the normal ILogger/DB-log-sink pipeline (ADR 0024) instead.
--
-- Copies the invoice's current [file] bytes into [dbo].[fds__invoice_file_backups],
-- then clears [file] so the document regenerates on next read (ADR 0025). Both steps
-- run in one transaction. Only a finalised, non-external invoice qualifies — the same
-- precondition ADR 0025 puts on regeneration: clearing a draft's file would let the
-- next read persist a draft render (fds__setInvoiceFile writes WHERE [file] IS NULL)
-- that then blocks the real final PDF forever; an external (MFR) document cannot be
-- regenerated at all. The calling service checks this too; this is defense in depth.
-- An empty OUTPUT rowset means nothing was backed up/cleared (invoice missing, not
-- finalised, external, or [file] already NULL) — rows-affected can't be used for this
-- because of SET NOCOUNT ON (same reasoning as fds__setInvoiceFile, ADR 0025).
-- =============================================
CREATE PROCEDURE [dbo].[fds__admin_backupAndClearInvoiceFile]
@authuser varchar(25),
@Id varchar(10)
AS
BEGIN
SET NOCOUNT ON;
-- The clear must never commit without its backup row: XACT_ABORT turns any statement-level
-- error inside the transaction into a doomed transaction, and the CATCH rolls back and re-raises
-- so the caller sees an error instead of an OUTPUT row (same pattern as the mfr__updt__* procs).
SET XACT_ABORT ON;
INSERT INTO [dbo].[fds__admin_activity] ([activity], [authuser], [info])
VALUES ('fds__admin_backupAndClearInvoiceFile', @authuser,
(SELECT * FROM (VALUES(@Id)) AS z([id]) FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER));
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO [dbo].[fds__invoice_file_backups] ([InvoiceId], [DocumentName], [file], [BackedUpBy])
SELECT [Id], [DocumentName], [file], @authuser
FROM [dbo].[fds__invoices]
WHERE [Id] = @Id AND [file] IS NOT NULL
AND [DateFinalized] IS NOT NULL AND ISNULL([IsExternal], 0) = 0;
UPDATE [dbo].[fds__invoices]
SET [file] = NULL
OUTPUT inserted.[Id]
WHERE [Id] = @Id AND [file] IS NOT NULL
AND [DateFinalized] IS NOT NULL AND ISNULL([IsExternal], 0) = 0;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
INSERT INTO dbo.[fds__admin_dberrors] ([UserName], [ErrorNumber], [ErrorState], [ErrorSeverity], [ErrorLine], [ErrorProcedure], [ErrorMessage], [ErrorDateTime])
VALUES (SUSER_SNAME(), ERROR_NUMBER(), ERROR_STATE(), ERROR_SEVERITY(), ERROR_LINE(), ERROR_PROCEDURE(), ERROR_MESSAGE(), GETDATE());
THROW;
END CATCH;
END
@@ -0,0 +1,13 @@
CREATE TABLE [dbo].[fds__invoice_file_backups] (
[uid] BIGINT IDENTITY (1, 1) NOT NULL,
[InvoiceId] VARCHAR (10) NOT NULL,
[DocumentName] VARCHAR (50) NULL,
[file] VARBINARY (MAX) NOT NULL,
[BackedUpAt] DATETIME CONSTRAINT [DF_fds__invoice_file_backups_BackedUpAt] DEFAULT (getutcdate()) NOT NULL,
[BackedUpBy] VARCHAR (25) NULL,
CONSTRAINT [PK_fds__invoice_file_backups] PRIMARY KEY CLUSTERED ([uid] ASC)
);
GO
CREATE NONCLUSTERED INDEX [fds__invoice_file_backups__ix1]
ON [dbo].[fds__invoice_file_backups]([InvoiceId] ASC);