feat: Implement mfr truncation fix and enhance invoice handling
Playwright Tests / test (push) Canceled after 0s
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:
@@ -0,0 +1,504 @@
|
||||
-- =============================================
|
||||
-- 2026-09-30 mfr sync: "String or binary data would be truncated" (msg 8152)
|
||||
--
|
||||
-- Root cause: mfr's free-text [Note] on ServiceObject and Company has no length limit
|
||||
-- (live mfr data on 2026-09-30: ServiceObject.Note up to 753 chars, Company.Note up to 723),
|
||||
-- but [mfr__serviceobjects].[Note], [mfr__companies].[Note] and their table types
|
||||
-- [mfr__tt__serviceobjects] / [mfr__tt__companies] were NVARCHAR(255). The merge procs
|
||||
-- [mfr__updt__serviceobjects] / [mfr__updt__companies] copy the staging table into the type,
|
||||
-- so every batch containing such a row (the entity's own sync and every Company/Contact/
|
||||
-- ServiceRequest/ServiceObject sync that $expands it) was rejected as a whole.
|
||||
--
|
||||
-- This script (idempotent, safe to re-run; one transaction, all-or-nothing):
|
||||
-- 1. patches the [@company] table variable in fds__getRequest_details, fds__prepInvoice
|
||||
-- and fds__prepStorno_recreate (they INSERT ... SELECT cy.* from [mfr__companies]):
|
||||
-- [Note] -> nvarchar(max) (without this, widening the table would move the truncation
|
||||
-- into invoice/request preparation for customers with a long note), and
|
||||
-- [CustomValues] / [MainContactId] -> NULL (both nullable in [mfr__companies]; the
|
||||
-- NOT NULL declaration raised 515 for a company without main contact). Patched IN
|
||||
-- PLACE on the deployed definition (only the @company block changes), so other edits
|
||||
-- to those procs already deployed on this server are kept. Same result as the SSDT
|
||||
-- versions of these procs.
|
||||
-- 2. ALTERs [mfr__serviceobjects].[Note] and [mfr__companies].[Note] to NVARCHAR(MAX).
|
||||
-- 3. Drops/recreates the table types [mfr__tt__serviceobjects] / [mfr__tt__companies]
|
||||
-- (a table type cannot be ALTERed). The procs that DECLARE them,
|
||||
-- [mfr__updt__serviceobjects] / [mfr__updt__companies], are dropped first and recreated
|
||||
-- from the SSDT source; explicit GRANT/DENY on proc and type are captured and re-applied.
|
||||
-- 4. Queues a re-sync (fds__mfr_updaterequests) for the rows that were rejected, because
|
||||
-- the short sync only fetches rows newer than the local watermark and would skip them.
|
||||
--
|
||||
-- Recommended: stop the Fuchs_DataService worker while the script runs.
|
||||
-- Matches SSDT (Fuchs_Database) as of 2026-09-30.
|
||||
-- =============================================
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
SET ANSI_NULLS ON;
|
||||
|
||||
DECLARE @type_so nvarchar(max) = N'CREATE TYPE [dbo].[mfr__tt__serviceobjects] AS TABLE (
|
||||
[setid] VARCHAR (5) NULL,
|
||||
[Id] BIGINT NOT NULL,
|
||||
[Location#ID] BIGINT NULL,
|
||||
[Name] NVARCHAR (255) NULL,
|
||||
[Note] NVARCHAR (MAX) NULL,
|
||||
[CustomValues] NVARCHAR (MAX) NOT NULL,
|
||||
[DateModified] DATETIME NOT NULL,
|
||||
[ExternalId] NVARCHAR (255) NULL,
|
||||
[MappingId] NVARCHAR (255) NULL,
|
||||
[QuickSearch] NVARCHAR (255) NULL,
|
||||
[Version] INT NOT NULL,
|
||||
[CreateGeoLocation] BIT NOT NULL,
|
||||
[IsWarehouse] BIT NOT NULL,
|
||||
[ParentServiceObjectId] BIGINT NOT NULL,
|
||||
[CompanyId] BIGINT NOT NULL,
|
||||
[ProductId] BIGINT NOT NULL);';
|
||||
|
||||
DECLARE @proc_so nvarchar(max) = N'-- =============================================
|
||||
-- Author: <Author,,Name>
|
||||
-- Create date: <Create Date,,>
|
||||
-- Description: <Description,,>
|
||||
-- =============================================
|
||||
CREATE PROCEDURE [dbo].[mfr__updt__serviceobjects]
|
||||
@tblname as nvarchar(50)
|
||||
, @referencetable nvarchar(50)
|
||||
, @tgtid bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
BEGIN TRY
|
||||
|
||||
DECLARE @now datetime = GETUTCDATE();
|
||||
DECLARE @tmp [dbo].[mfr__tt__serviceobjects];
|
||||
|
||||
DECLARE @tmp_cmd nvarchar(1000) = N''SELECT DISTINCT * FROM '' + @tblname
|
||||
INSERT INTO @tmp EXECUTE [sp_executesql] @tmp_cmd;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
IF @referencetable = ''mfr__serviceobjects''
|
||||
BEGIN
|
||||
DELETE p
|
||||
--OUTput deleted.*, @now INTO [dbo].[mfr__d_*PartnerSet]
|
||||
FROM [dbo].[mfr__*PartnerSet] as p
|
||||
WHERE p.Property like ''ServiceObject[:]%'' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = p.[EntityId]);
|
||||
DELETE cv
|
||||
--OUTput deleted.*, @now INTO [dbo].[mfr__d_#customvalues]
|
||||
FROM [dbo].[mfr__#customvalues] as cv
|
||||
WHERE cv.Property like ''ServiceObject[:]%'' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cv.[EntityId]);
|
||||
DELETE lc
|
||||
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_#locations]
|
||||
FROM [dbo].[mfr__#locations] as lc
|
||||
WHERE lc.Property like ''ServiceObject[:]%'' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = lc.[EntityId]);
|
||||
DELETE itm
|
||||
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_items]
|
||||
FROM [dbo].[mfr__items] as itm
|
||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = itm.[ServiceObjectId] and ISNULL(itm.[ServiceRequestId],0) = 0);
|
||||
END
|
||||
|
||||
|
||||
MERGE [dbo].[mfr__serviceobjects] as TARGET
|
||||
USING @tmp as SOURCE ON TARGET.[Id] = SOURCE.[Id]
|
||||
WHEN MATCHED AND (TARGET.[Version] <= SOURCE.[Version] OR TARGET.[DateModified] <= SOURCE.[DateModified]) THEN
|
||||
UPDATE
|
||||
SET [Id] = SOURCE.[Id]
|
||||
,[Location#ID] = SOURCE.[Location#ID]
|
||||
,[Name] = SOURCE.[Name]
|
||||
,[Note] = SOURCE.[Note]
|
||||
,[CustomValues] = SOURCE.[CustomValues]
|
||||
,[DateModified] = SOURCE.[DateModified]
|
||||
,[ExternalId] = SOURCE.[ExternalId]
|
||||
,[MappingId] = SOURCE.[MappingId]
|
||||
,[QuickSearch] = SOURCE.[QuickSearch]
|
||||
,[Version] = SOURCE.[Version]
|
||||
,[CreateGeoLocation] = SOURCE.[CreateGeoLocation]
|
||||
,[IsWarehouse] = SOURCE.[IsWarehouse]
|
||||
,[ParentServiceObjectId] = SOURCE.[ParentServiceObjectId]
|
||||
,[CompanyId] = SOURCE.[CompanyId]
|
||||
,[ProductId] = SOURCE.[ProductId]
|
||||
WHEN NOT MATCHED BY TARGET THEN
|
||||
INSERT
|
||||
( [Id]
|
||||
,[Location#ID]
|
||||
,[Name]
|
||||
,[Note]
|
||||
,[CustomValues]
|
||||
,[DateModified]
|
||||
,[ExternalId]
|
||||
,[MappingId]
|
||||
,[QuickSearch]
|
||||
,[Version]
|
||||
,[CreateGeoLocation]
|
||||
,[IsWarehouse]
|
||||
,[ParentServiceObjectId]
|
||||
,[CompanyId]
|
||||
,[ProductId])
|
||||
VALUES
|
||||
(SOURCE.[Id]
|
||||
,SOURCE.[Location#ID]
|
||||
,SOURCE.[Name]
|
||||
,SOURCE.[Note]
|
||||
,SOURCE.[CustomValues]
|
||||
,SOURCE.[DateModified]
|
||||
,SOURCE.[ExternalId]
|
||||
,SOURCE.[MappingId]
|
||||
,SOURCE.[QuickSearch]
|
||||
,SOURCE.[Version]
|
||||
,SOURCE.[CreateGeoLocation]
|
||||
,SOURCE.[IsWarehouse]
|
||||
,SOURCE.[ParentServiceObjectId]
|
||||
,SOURCE.[CompanyId]
|
||||
,SOURCE.[ProductId]);
|
||||
|
||||
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';
|
||||
|
||||
DECLARE @type_co nvarchar(max) = N'CREATE TYPE [dbo].[mfr__tt__companies] AS TABLE (
|
||||
[setid] VARCHAR (5) NULL,
|
||||
[Id] BIGINT NOT NULL,
|
||||
[Location#ID] BIGINT NULL,
|
||||
[Version] INT NOT NULL,
|
||||
[IsPhysicalPerson] BIT NOT NULL,
|
||||
[IsOwner] BIT NOT NULL,
|
||||
[IsEmailInvoicingActive] BIT NOT NULL,
|
||||
[IsSupplier] BIT NOT NULL,
|
||||
[MappingId] NVARCHAR (255) NULL,
|
||||
[ExternalId] NVARCHAR (255) NULL,
|
||||
[Name] NVARCHAR (255) NULL,
|
||||
[Note] NVARCHAR (MAX) NULL,
|
||||
[SupportTelephone] NVARCHAR (255) NULL,
|
||||
[SupportFax] NVARCHAR (255) NULL,
|
||||
[SupportMail] NVARCHAR (255) NULL,
|
||||
[QuickSearch] NVARCHAR (255) NULL,
|
||||
[DateModified] DATETIME NOT NULL,
|
||||
[CustomValues] NVARCHAR (MAX) NULL,
|
||||
[MainContactId] BIGINT NULL);';
|
||||
|
||||
DECLARE @proc_co nvarchar(max) = N'-- =============================================
|
||||
-- Description: mfr sync — merges one staged batch of Company entities into [mfr__companies].
|
||||
-- @tblname is the bulk-copied staging table (same layout as [mfr__tt__companies]);
|
||||
-- [setid] is the batch marker. A full entity reset (@referencetable = ''mfr__companies'')
|
||||
-- first drops the company''s dependent partner/custom-value/location/comment rows so
|
||||
-- they are rebuilt from the same batch. Duplicate Ids within one batch (paged reads
|
||||
-- overlapping a concurrent mfr edit) are collapsed to the highest Version before the
|
||||
-- MERGE, otherwise MERGE raises 8672.
|
||||
-- =============================================
|
||||
CREATE PROCEDURE [dbo].[mfr__updt__companies]
|
||||
@tblname as nvarchar(50)
|
||||
, @referencetable nvarchar(50)
|
||||
, @tgtid bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
BEGIN TRY
|
||||
|
||||
DECLARE @tmp [dbo].[mfr__tt__companies];
|
||||
|
||||
DECLARE @tmp_cmd nvarchar(1000) = N''SELECT DISTINCT * FROM '' + @tblname
|
||||
INSERT INTO @tmp EXECUTE [sp_executesql] @tmp_cmd;
|
||||
|
||||
-- Wrapping the DELETEs + MERGE below in one transaction gives all-or-nothing atomicity for
|
||||
-- this entity''s sync batch (same pattern in every mfr__updt__* proc). During a large full
|
||||
-- reset this can hold locks on the affected tables for as long as the batch takes to run —
|
||||
-- an accepted, documented risk, not an oversight. See
|
||||
-- Docs/Decisions/0022-mfr-sync-transaction-blocking-accepted-risk.md before changing this.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
IF @referencetable = ''mfr__companies''
|
||||
BEGIN
|
||||
DELETE p
|
||||
--OUTput deleted.*, @now INTO [dbo].[mfr__d_*PartnerSet]
|
||||
FROM [dbo].[mfr__*PartnerSet] as p
|
||||
WHERE p.Property like ''Company[:]%'' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = p.[EntityId]);
|
||||
DELETE cv
|
||||
--OUTput deleted.*, @now INTO [dbo].[mfr__d_#customvalues]
|
||||
FROM [dbo].[mfr__#customvalues] as cv
|
||||
WHERE cv.Property like ''Company[:]%'' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cv.[EntityId]);
|
||||
DELETE lc
|
||||
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_#locations]
|
||||
FROM [dbo].[mfr__#locations] as lc
|
||||
WHERE lc.Property like ''Company[:]%'' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = lc.[EntityId]);
|
||||
DELETE cm
|
||||
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_comments]
|
||||
FROM [dbo].[mfr__comments] as cm
|
||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cm.[CompanyId] AND ISNULL(cm.[ServiceRequestId],0) = 0);
|
||||
END;
|
||||
|
||||
WITH tmp as (
|
||||
SELECT *, ROW_NUMBER() OVER (PARTITION BY [Id] ORDER BY [Version] DESC, [DateModified] DESC) as ''#'' FROM @tmp
|
||||
), SOURCE as (
|
||||
SELECT * FROM tmp WHERE [#] = 1
|
||||
)
|
||||
MERGE [dbo].[mfr__companies] as TARGET
|
||||
USING SOURCE ON TARGET.[Id] = SOURCE.[Id]
|
||||
WHEN MATCHED AND (TARGET.[Version] <= SOURCE.[Version] OR TARGET.[DateModified] <= SOURCE.[DateModified]) THEN
|
||||
UPDATE
|
||||
SET [Id] = SOURCE.[Id]
|
||||
,[Location#ID] = SOURCE.[Location#ID]
|
||||
,[Version] = SOURCE.[Version]
|
||||
,[IsPhysicalPerson] = SOURCE.[IsPhysicalPerson]
|
||||
,[IsOwner] = SOURCE.[IsOwner]
|
||||
,[IsEmailInvoicingActive] = SOURCE.[IsEmailInvoicingActive]
|
||||
,[IsSupplier] = SOURCE.[IsSupplier]
|
||||
,[MappingId] = SOURCE.[MappingId]
|
||||
,[ExternalId] = SOURCE.[ExternalId]
|
||||
,[Name] = SOURCE.[Name]
|
||||
,[Note] = SOURCE.[Note]
|
||||
,[SupportTelephone] = SOURCE.[SupportTelephone]
|
||||
,[SupportFax] = SOURCE.[SupportFax]
|
||||
,[SupportMail] = SOURCE.[SupportMail]
|
||||
,[QuickSearch] = SOURCE.[QuickSearch]
|
||||
,[DateModified] = SOURCE.[DateModified]
|
||||
,[CustomValues] = SOURCE.[CustomValues]
|
||||
,[MainContactId] = SOURCE.[MainContactId]
|
||||
WHEN NOT MATCHED BY TARGET THEN
|
||||
INSERT
|
||||
( [Id]
|
||||
,[Location#ID]
|
||||
,[Version]
|
||||
,[IsPhysicalPerson]
|
||||
,[IsOwner]
|
||||
,[IsEmailInvoicingActive]
|
||||
,[IsSupplier]
|
||||
,[MappingId]
|
||||
,[ExternalId]
|
||||
,[Name]
|
||||
,[Note]
|
||||
,[SupportTelephone]
|
||||
,[SupportFax]
|
||||
,[SupportMail]
|
||||
,[QuickSearch]
|
||||
,[DateModified]
|
||||
,[CustomValues]
|
||||
,[MainContactId])
|
||||
VALUES
|
||||
(SOURCE.[Id]
|
||||
,SOURCE.[Location#ID]
|
||||
,SOURCE.[Version]
|
||||
,SOURCE.[IsPhysicalPerson]
|
||||
,SOURCE.[IsOwner]
|
||||
,SOURCE.[IsEmailInvoicingActive]
|
||||
,SOURCE.[IsSupplier]
|
||||
,SOURCE.[MappingId]
|
||||
,SOURCE.[ExternalId]
|
||||
,SOURCE.[Name]
|
||||
,SOURCE.[Note]
|
||||
,SOURCE.[SupportTelephone]
|
||||
,SOURCE.[SupportFax]
|
||||
,SOURCE.[SupportMail]
|
||||
,SOURCE.[QuickSearch]
|
||||
,SOURCE.[DateModified]
|
||||
,SOURCE.[CustomValues]
|
||||
,SOURCE.[MainContactId]);
|
||||
|
||||
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';
|
||||
|
||||
DECLARE @perms TABLE ([stmt] nvarchar(max) NOT NULL);
|
||||
DECLARE @stmt nvarchar(max);
|
||||
|
||||
BEGIN TRY
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- ---- 1. the [@company] table variable in the invoice/request preparation procs
|
||||
-- Three column changes, matching the SSDT versions of these procs:
|
||||
-- [Note] nvarchar(255) -> nvarchar(max) (this fix)
|
||||
-- [CustomValues] NOT NULL -> NULL (nullable in [mfr__companies]; NOT NULL here
|
||||
-- [MainContactId] NOT NULL -> NULL raised 515 on INSERT ... SELECT cy.* for a
|
||||
-- company without main contact / custom values)
|
||||
-- Only the "DECLARE @company TABLE ( ... );" block is edited: the same column names occur in other
|
||||
-- table variables of these procs (e.g. @SReq.[CustomValues] NOT NULL), which must stay as they are.
|
||||
DECLARE @procname sysname, @def nvarchar(max), @pos int, @i int, @blkStart int, @blkEnd int, @blk nvarchar(max), @blkNew nvarchar(max);
|
||||
DECLARE @blkMarker nvarchar(50) = N'DECLARE @company TABLE';
|
||||
DECLARE proc_cur CURSOR LOCAL FAST_FORWARD FOR
|
||||
SELECT v.[name] FROM (VALUES (N'fds__getRequest_details'), (N'fds__prepInvoice'), (N'fds__prepStorno_recreate')) AS v([name]);
|
||||
OPEN proc_cur;
|
||||
FETCH NEXT FROM proc_cur INTO @procname;
|
||||
WHILE @@FETCH_STATUS = 0
|
||||
BEGIN
|
||||
SET @def = OBJECT_DEFINITION(OBJECT_ID(N'[dbo].' + QUOTENAME(@procname)));
|
||||
-- Locate the CREATE keyword of "CREATE <whitespace> PROCEDURE" (deployed headers vary in spacing).
|
||||
SET @pos = CHARINDEX(N'PROCEDURE', ISNULL(@def, N''));
|
||||
SET @i = @pos - 1;
|
||||
WHILE @i > 0 AND SUBSTRING(@def, @i, 1) IN (N' ', NCHAR(9), NCHAR(10), NCHAR(13)) SET @i -= 1;
|
||||
SET @pos = CASE WHEN @pos > 0 AND @i >= 6 AND SUBSTRING(@def, @i - 5, 6) = N'CREATE' THEN @i - 5 ELSE 0 END;
|
||||
-- Locate the single "DECLARE @company TABLE ( ... );" block (ends at the first ');' after the marker).
|
||||
SET @blkStart = CHARINDEX(@blkMarker, ISNULL(@def, N''));
|
||||
SET @blkEnd = CASE WHEN @blkStart > 0 THEN CHARINDEX(N');', @def, @blkStart) ELSE 0 END;
|
||||
IF @def IS NULL
|
||||
RAISERROR(N'WARNING: [dbo].[%s] not found - skipped.', 10, 1, @procname) WITH NOWAIT;
|
||||
ELSE IF @pos = 0 OR @blkStart = 0 OR @blkEnd = 0 OR CHARINDEX(@blkMarker, @def, @blkStart + 1) > 0
|
||||
RAISERROR(N'WARNING: [dbo].[%s] could not be patched automatically (no CREATE PROCEDURE header, or the @company TABLE block is missing/ambiguous) - edit its @company declaration by hand: [Note] nvarchar(max), [CustomValues] NULL, [MainContactId] NULL.', 10, 1, @procname) WITH NOWAIT;
|
||||
ELSE
|
||||
BEGIN
|
||||
SET @blk = SUBSTRING(@def, @blkStart, @blkEnd - @blkStart + 2);
|
||||
SET @blkNew = REPLACE(@blk, N'[Note] [nvarchar](255) NULL', N'[Note] [nvarchar](max) NULL');
|
||||
SET @blkNew = REPLACE(@blkNew, N'[CustomValues] [nvarchar](max) NOT NULL', N'[CustomValues] [nvarchar](max) NULL');
|
||||
SET @blkNew = REPLACE(@blkNew, N'[MainContactId] [bigint] NOT NULL', N'[MainContactId] [bigint] NULL');
|
||||
IF @blkNew = @blk
|
||||
PRINT CONCAT(N'[', @procname, N'] @company block already matches SSDT - skipped.');
|
||||
ELSE
|
||||
BEGIN
|
||||
-- block first (it lies after the header), then CREATE -> ALTER (changes the length before it)
|
||||
SET @def = STUFF(@def, @blkStart, DATALENGTH(@blk) / 2, @blkNew);
|
||||
SET @def = STUFF(@def, @pos, LEN(N'CREATE'), N'ALTER');
|
||||
EXEC (@def);
|
||||
PRINT CONCAT(N'[', @procname, N'] @company block patched: [Note] nvarchar(max), [CustomValues] NULL, [MainContactId] NULL.');
|
||||
END
|
||||
END
|
||||
FETCH NEXT FROM proc_cur INTO @procname;
|
||||
END
|
||||
CLOSE proc_cur;
|
||||
DEALLOCATE proc_cur;
|
||||
|
||||
-- ---- 2. target tables
|
||||
IF EXISTS (SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(N'[dbo].[mfr__serviceobjects]') AND [name] = N'Note' AND [max_length] <> -1)
|
||||
BEGIN
|
||||
ALTER TABLE [dbo].[mfr__serviceobjects] ALTER COLUMN [Note] NVARCHAR (MAX) NULL;
|
||||
PRINT N'[mfr__serviceobjects].[Note] widened to NVARCHAR(MAX).';
|
||||
END
|
||||
ELSE PRINT N'[mfr__serviceobjects].[Note] already NVARCHAR(MAX) - skipped.';
|
||||
|
||||
IF EXISTS (SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(N'[dbo].[mfr__companies]') AND [name] = N'Note' AND [max_length] <> -1)
|
||||
BEGIN
|
||||
ALTER TABLE [dbo].[mfr__companies] ALTER COLUMN [Note] NVARCHAR (MAX) NULL;
|
||||
PRINT N'[mfr__companies].[Note] widened to NVARCHAR(MAX).';
|
||||
END
|
||||
ELSE PRINT N'[mfr__companies].[Note] already NVARCHAR(MAX) - skipped.';
|
||||
|
||||
-- ---- 3. table types (+ the merge procs that declare them)
|
||||
|
||||
-- ---- [mfr__tt__serviceobjects] + [mfr__updt__serviceobjects]
|
||||
IF EXISTS (SELECT * FROM sys.table_types AS tt JOIN sys.columns AS c ON c.[object_id] = tt.[type_table_object_id]
|
||||
WHERE tt.[schema_id] = SCHEMA_ID(N'dbo') AND tt.[name] = N'mfr__tt__serviceobjects' AND c.[name] = N'Note' AND c.[max_length] <> -1)
|
||||
BEGIN
|
||||
-- A table type cannot be ALTERed. [mfr__updt__serviceobjects] DECLAREs it, so the proc is dropped
|
||||
-- first and recreated from the SSDT source afterwards; explicit grants are re-applied.
|
||||
INSERT INTO @perms ([stmt])
|
||||
SELECT CONCAT(CASE p.[state] WHEN 'D' THEN N'DENY ' ELSE N'GRANT ' END, p.[permission_name],
|
||||
CASE p.[class] WHEN 1 THEN N' ON OBJECT::[dbo].' + QUOTENAME(o.[name]) ELSE N' ON TYPE::[dbo].' + QUOTENAME(t.[name]) END,
|
||||
N' TO ', QUOTENAME(USER_NAME(p.[grantee_principal_id])),
|
||||
CASE p.[state] WHEN 'W' THEN N' WITH GRANT OPTION' ELSE N'' END, N';')
|
||||
FROM sys.database_permissions AS p
|
||||
LEFT JOIN sys.objects AS o ON p.[class] = 1 AND o.[object_id] = p.[major_id]
|
||||
LEFT JOIN sys.types AS t ON p.[class] = 6 AND t.[user_type_id] = p.[major_id]
|
||||
WHERE (p.[class] = 1 AND o.[object_id] = OBJECT_ID(N'[dbo].[mfr__updt__serviceobjects]'))
|
||||
OR (p.[class] = 6 AND t.[name] = N'mfr__tt__serviceobjects' AND t.[schema_id] = SCHEMA_ID(N'dbo'));
|
||||
|
||||
IF OBJECT_ID(N'[dbo].[mfr__updt__serviceobjects]', N'P') IS NOT NULL DROP PROCEDURE [dbo].[mfr__updt__serviceobjects];
|
||||
DROP TYPE [dbo].[mfr__tt__serviceobjects];
|
||||
EXEC (@type_so);
|
||||
EXEC (@proc_so);
|
||||
PRINT N'[mfr__tt__serviceobjects] recreated with [Note] NVARCHAR(MAX); [mfr__updt__serviceobjects] recreated.';
|
||||
END
|
||||
ELSE PRINT N'[mfr__tt__serviceobjects].[Note] already NVARCHAR(MAX) - skipped.';
|
||||
|
||||
-- ---- [mfr__tt__companies] + [mfr__updt__companies]
|
||||
IF EXISTS (SELECT * FROM sys.table_types AS tt JOIN sys.columns AS c ON c.[object_id] = tt.[type_table_object_id]
|
||||
WHERE tt.[schema_id] = SCHEMA_ID(N'dbo') AND tt.[name] = N'mfr__tt__companies' AND c.[name] = N'Note' AND c.[max_length] <> -1)
|
||||
BEGIN
|
||||
-- A table type cannot be ALTERed. [mfr__updt__companies] DECLAREs it, so the proc is dropped
|
||||
-- first and recreated from the SSDT source afterwards; explicit grants are re-applied.
|
||||
INSERT INTO @perms ([stmt])
|
||||
SELECT CONCAT(CASE p.[state] WHEN 'D' THEN N'DENY ' ELSE N'GRANT ' END, p.[permission_name],
|
||||
CASE p.[class] WHEN 1 THEN N' ON OBJECT::[dbo].' + QUOTENAME(o.[name]) ELSE N' ON TYPE::[dbo].' + QUOTENAME(t.[name]) END,
|
||||
N' TO ', QUOTENAME(USER_NAME(p.[grantee_principal_id])),
|
||||
CASE p.[state] WHEN 'W' THEN N' WITH GRANT OPTION' ELSE N'' END, N';')
|
||||
FROM sys.database_permissions AS p
|
||||
LEFT JOIN sys.objects AS o ON p.[class] = 1 AND o.[object_id] = p.[major_id]
|
||||
LEFT JOIN sys.types AS t ON p.[class] = 6 AND t.[user_type_id] = p.[major_id]
|
||||
WHERE (p.[class] = 1 AND o.[object_id] = OBJECT_ID(N'[dbo].[mfr__updt__companies]'))
|
||||
OR (p.[class] = 6 AND t.[name] = N'mfr__tt__companies' AND t.[schema_id] = SCHEMA_ID(N'dbo'));
|
||||
|
||||
IF OBJECT_ID(N'[dbo].[mfr__updt__companies]', N'P') IS NOT NULL DROP PROCEDURE [dbo].[mfr__updt__companies];
|
||||
DROP TYPE [dbo].[mfr__tt__companies];
|
||||
EXEC (@type_co);
|
||||
EXEC (@proc_co);
|
||||
PRINT N'[mfr__tt__companies] recreated with [Note] NVARCHAR(MAX); [mfr__updt__companies] recreated.';
|
||||
END
|
||||
ELSE PRINT N'[mfr__tt__companies].[Note] already NVARCHAR(MAX) - skipped.';
|
||||
|
||||
DECLARE perm_cur CURSOR LOCAL FAST_FORWARD FOR SELECT [stmt] FROM @perms;
|
||||
OPEN perm_cur;
|
||||
FETCH NEXT FROM perm_cur INTO @stmt;
|
||||
WHILE @@FETCH_STATUS = 0
|
||||
BEGIN
|
||||
EXEC (@stmt);
|
||||
PRINT N'Re-applied: ' + @stmt;
|
||||
FETCH NEXT FROM perm_cur INTO @stmt;
|
||||
END
|
||||
CLOSE perm_cur;
|
||||
DEALLOCATE perm_cur;
|
||||
|
||||
-- ---- 4. queue a re-sync of the rejected rows
|
||||
-- Rows with a >255-char Note in mfr on 2026-09-30 (read-only scan of the mfr OData API).
|
||||
DECLARE @resync TABLE ([entity_name] varchar(255) NOT NULL, [Id] bigint NOT NULL);
|
||||
INSERT INTO @resync ([entity_name], [Id]) VALUES
|
||||
('ServiceObject', 24296783880), ('ServiceObject', 34080915468), ('ServiceObject', 38722404355)
|
||||
,('ServiceObject', 105289089024), ('ServiceObject', 105292464132), ('ServiceObject', 105292464134)
|
||||
,('Company', 104430043136), ('Company', 105288990720);
|
||||
-- Plus every row of the last rejected batch kept by the data service (if present).
|
||||
IF OBJECT_ID(N'[dbo].[t_err_mfr__serviceobjects]', N'U') IS NOT NULL
|
||||
INSERT INTO @resync EXEC (N'SELECT DISTINCT ''ServiceObject'', CAST([Id] AS bigint) FROM [dbo].[t_err_mfr__serviceobjects] WHERE [Id] IS NOT NULL;');
|
||||
IF OBJECT_ID(N'[dbo].[t_err_mfr__companies]', N'U') IS NOT NULL
|
||||
INSERT INTO @resync EXEC (N'SELECT DISTINCT ''Company'', CAST([Id] AS bigint) FROM [dbo].[t_err_mfr__companies] WHERE [Id] IS NOT NULL;');
|
||||
|
||||
INSERT INTO [dbo].[fds__mfr_updaterequests] ([entity_name], [Id])
|
||||
SELECT DISTINCT r.[entity_name], r.[Id] FROM @resync AS r
|
||||
WHERE NOT EXISTS (SELECT * FROM [dbo].[fds__mfr_updaterequests] AS q
|
||||
WHERE q.[entity_name] = r.[entity_name] AND q.[Id] = r.[Id]);
|
||||
PRINT CONCAT(N'Re-sync requests queued: ', @@ROWCOUNT);
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
PRINT N'mfr truncation fix applied.';
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
|
||||
THROW;
|
||||
END CATCH;
|
||||
GO
|
||||
|
||||
-- ---- verification
|
||||
-- Expect max_length = -1 on all four rows.
|
||||
SELECT [object] = o.[name], [column] = c.[name], c.[max_length]
|
||||
FROM sys.columns AS c JOIN sys.objects AS o ON o.[object_id] = c.[object_id]
|
||||
WHERE c.[name] = N'Note' AND o.[name] IN (N'mfr__serviceobjects', N'mfr__companies')
|
||||
UNION ALL
|
||||
SELECT tt.[name], c.[name], c.[max_length]
|
||||
FROM sys.table_types AS tt JOIN sys.columns AS c ON c.[object_id] = tt.[type_table_object_id]
|
||||
WHERE c.[name] = N'Note' AND tt.[name] IN (N'mfr__tt__serviceobjects', N'mfr__tt__companies');
|
||||
-- Expect no row (every @company block widened and made nullable; other table variables are not inspected).
|
||||
SELECT [still_unpatched] = o.[name], [company_block] = b.[blk]
|
||||
FROM sys.sql_modules AS m JOIN sys.objects AS o ON o.[object_id] = m.[object_id]
|
||||
CROSS APPLY (SELECT [s] = CHARINDEX(N'DECLARE @company TABLE', m.[definition])) AS p
|
||||
CROSS APPLY (SELECT [blk] = CASE WHEN p.[s] > 0 THEN SUBSTRING(m.[definition], p.[s], CHARINDEX(N');', m.[definition], p.[s]) - p.[s] + 2) ELSE N'<no @company block>' END) AS b
|
||||
WHERE o.[name] IN (N'fds__getRequest_details', N'fds__prepInvoice', N'fds__prepStorno_recreate')
|
||||
AND (b.[blk] LIKE N'%[[]Note] [[]nvarchar](255)%'
|
||||
OR b.[blk] LIKE N'%[[]CustomValues] [[]nvarchar](max) NOT NULL%'
|
||||
OR b.[blk] LIKE N'%[[]MainContactId] [[]bigint] NOT NULL%'
|
||||
OR p.[s] = 0);
|
||||
-- Once the worker has run, the queue drains and the counts approach mfr's
|
||||
-- (2026-09-30: 5531 ServiceObjects, 4625 Companies).
|
||||
SELECT [queued] = COUNT(*) FROM [dbo].[fds__mfr_updaterequests] WHERE [entity_name] IN ('ServiceObject', 'Company');
|
||||
SELECT [serviceobjects] = (SELECT COUNT(*) FROM [dbo].[mfr__serviceobjects]), [companies] = (SELECT COUNT(*) FROM [dbo].[mfr__companies]);
|
||||
Reference in New Issue
Block a user