diff --git a/PlotLine/Data/StoryIntelligenceResultRepository.cs b/PlotLine/Data/StoryIntelligenceResultRepository.cs index 0d66b7c..d50a78d 100644 --- a/PlotLine/Data/StoryIntelligenceResultRepository.cs +++ b/PlotLine/Data/StoryIntelligenceResultRepository.cs @@ -404,6 +404,7 @@ public sealed class StoryIntelligenceResultRepository(ISqlConnectionFactory conn var scenesCreated = 0; var metricsCreated = 0; + var commitWarnings = new List(); try { var alreadyCommitted = await connection.QuerySingleAsync( @@ -440,97 +441,149 @@ public sealed class StoryIntelligenceResultRepository(ISqlConnectionFactory conn for (var index = 0; index < orderedScenes.Count; index++) { var item = orderedScenes[index]; + var operation = "preparing scene"; var replacePlaceholder = readiness.HasUntouchedDefaultPlaceholder && readiness.PlaceholderSceneID.HasValue && index == 0; - var sceneId = await connection.QuerySingleAsync( - "dbo.Scene_Save", - new - { - SceneID = replacePlaceholder ? readiness.PlaceholderSceneID : null, - ChapterID = request.ChapterID, - SceneNumber = Convert.ToDecimal(item.TemporarySceneNumber), - SceneTitle = item.SceneTitle, - Summary = item.Summary, - TimeModeID = request.TimeModeID, - StartDateTime = (DateTime?)null, - EndDateTime = (DateTime?)null, - DurationAmount = (decimal?)null, - DurationUnitID = (int?)null, - RelativeTimeText = item.ParsedScene.Setting?.DateOrTimeReference ?? item.ParsedScene.Setting?.TimeOfDay, - TimeConfidenceID = request.TimeConfidenceID, - ScenePurposeNotes = item.PurposeNotes, - SceneOutcomeNotes = item.OutcomeNotes, - RevisionStatusID = request.RevisionStatusID, - PrimaryLocationID = (int?)null, - FloorPlanID = (int?)null, - InitialFloorPlanFloorID = (int?)null - }, - transaction, - commandType: CommandType.StoredProcedure); - - await connection.ExecuteAsync( - "dbo.StoryIntelligenceSceneSource_Save", - new - { - SceneID = sceneId, - ImportSource = "Story Intelligence", - ImportRunID = request.StoryIntelligenceRunID, - SourceStartParagraph = item.StartParagraph, - SourceEndParagraph = item.EndParagraph - }, - transaction, - commandType: CommandType.StoredProcedure); - - if (item.PovCharacterID.HasValue) + try { - await connection.ExecuteAsync( - "dbo.Scene_UpdatePovCharacter", - new { SceneID = sceneId, POVCharacterID = item.PovCharacterID }, - transaction, - commandType: CommandType.StoredProcedure); - } + operation = "saving scene"; + var relativeTimeText = TrimOptionalText( + item.ParsedScene.Setting?.DateOrTimeReference ?? item.ParsedScene.Setting?.TimeOfDay, + 200, + $"Scene {item.TemporarySceneNumber:N0} relative time", + commitWarnings); - if (item.PurposeTypeIDs.Count > 0) - { - await connection.ExecuteAsync( - "dbo.Scene_SavePurposes", - new { SceneID = sceneId, PurposeIds = string.Join(',', item.PurposeTypeIDs.Distinct()) }, - transaction, - commandType: CommandType.StoredProcedure); - } + var sceneTitle = TrimOptionalText(item.SceneTitle, 200, $"Scene {item.TemporarySceneNumber:N0} title", commitWarnings) + ?? $"Scene {item.TemporarySceneNumber:N0}"; - foreach (var metric in item.Metrics) - { - await connection.ExecuteAsync( - "dbo.SceneMetric_SaveValue", - new { SceneID = sceneId, metric.MetricTypeID, metric.Value, metric.Notes }, - transaction, - commandType: CommandType.StoredProcedure); - metricsCreated++; - } - - if (request.TimelineNoteTypeID.HasValue && !string.IsNullOrWhiteSpace(item.ImportNoteText)) - { - await connection.ExecuteAsync( - "dbo.SceneNote_Save", + var sceneId = await connection.QuerySingleAsync( + "dbo.Scene_Save", new { - SceneNoteID = (int?)null, - SceneID = sceneId, - SceneNoteTypeID = request.TimelineNoteTypeID.Value, - NoteTitle = "Story Intelligence import notes", - NoteText = item.ImportNoteText, - SortOrder = 10, - IsPinned = false, - IsResolved = false + SceneID = replacePlaceholder ? readiness.PlaceholderSceneID : null, + ChapterID = request.ChapterID, + SceneNumber = Convert.ToDecimal(item.TemporarySceneNumber), + SceneTitle = sceneTitle, + Summary = item.Summary, + TimeModeID = request.TimeModeID, + StartDateTime = (DateTime?)null, + EndDateTime = (DateTime?)null, + DurationAmount = (decimal?)null, + DurationUnitID = (int?)null, + RelativeTimeText = relativeTimeText, + TimeConfidenceID = request.TimeConfidenceID, + ScenePurposeNotes = item.PurposeNotes, + SceneOutcomeNotes = item.OutcomeNotes, + RevisionStatusID = request.RevisionStatusID, + PrimaryLocationID = (int?)null, + FloorPlanID = (int?)null, + InitialFloorPlanFloorID = (int?)null }, transaction, commandType: CommandType.StoredProcedure); - } - scenesCreated++; + operation = "saving source tracking"; + await connection.ExecuteAsync( + "dbo.StoryIntelligenceSceneSource_Save", + new + { + SceneID = sceneId, + ImportSource = "Story Intelligence", + ImportRunID = request.StoryIntelligenceRunID, + SourceStartParagraph = item.StartParagraph, + SourceEndParagraph = item.EndParagraph + }, + transaction, + commandType: CommandType.StoredProcedure); + + if (item.PovCharacterID.HasValue) + { + operation = "linking POV character"; + await TryOptionalSceneOperationAsync( + connection, + transaction, + item.TemporarySceneNumber, + operation, + commitWarnings, + () => connection.ExecuteAsync( + "dbo.Scene_UpdatePovCharacter", + new { SceneID = sceneId, POVCharacterID = item.PovCharacterID }, + transaction, + commandType: CommandType.StoredProcedure)); + } + + if (item.PurposeTypeIDs.Count > 0) + { + operation = "linking purpose labels"; + await TryOptionalSceneOperationAsync( + connection, + transaction, + item.TemporarySceneNumber, + operation, + commitWarnings, + () => connection.ExecuteAsync( + "dbo.Scene_SavePurposes", + new { SceneID = sceneId, PurposeIds = string.Join(',', item.PurposeTypeIDs.Distinct()) }, + transaction, + commandType: CommandType.StoredProcedure)); + } + + foreach (var metric in item.Metrics) + { + operation = $"saving metric '{metric.MetricName}'"; + var saved = await TryOptionalSceneOperationAsync( + connection, + transaction, + item.TemporarySceneNumber, + operation, + commitWarnings, + () => connection.ExecuteAsync( + "dbo.SceneMetric_SaveValue", + new { SceneID = sceneId, metric.MetricTypeID, metric.Value, metric.Notes }, + transaction, + commandType: CommandType.StoredProcedure)); + if (saved) + { + metricsCreated++; + } + } + + if (request.TimelineNoteTypeID.HasValue && !string.IsNullOrWhiteSpace(item.ImportNoteText)) + { + operation = "saving import note"; + await TryOptionalSceneOperationAsync( + connection, + transaction, + item.TemporarySceneNumber, + operation, + commitWarnings, + () => connection.ExecuteAsync( + "dbo.SceneNote_Save", + new + { + SceneNoteID = (int?)null, + SceneID = sceneId, + SceneNoteTypeID = request.TimelineNoteTypeID.Value, + NoteTitle = "Story Intelligence import notes", + NoteText = item.ImportNoteText, + SortOrder = 10, + IsPinned = false, + IsResolved = false + }, + transaction, + commandType: CommandType.StoredProcedure)); + } + + scenesCreated++; + } + catch (Exception ex) + { + throw new InvalidOperationException( + $"Scene {item.TemporarySceneNumber:N0} failed during {operation}: {ex.Message}", + ex); + } } var notes = new List(); @@ -538,6 +591,7 @@ public sealed class StoryIntelligenceResultRepository(ISqlConnectionFactory conn ? "Replaced untouched default placeholder scene." : "Committed into empty chapter."); notes.AddRange(request.Warnings.Where(warning => !string.IsNullOrWhiteSpace(warning))); + notes.AddRange(commitWarnings.Where(warning => !string.IsNullOrWhiteSpace(warning))); var commitId = await connection.QuerySingleAsync( "dbo.StoryIntelligenceImportCommit_RecordCompleted", @@ -568,7 +622,7 @@ public sealed class StoryIntelligenceResultRepository(ISqlConnectionFactory conn catch (Exception ex) { transaction.Rollback(); - await RecordFailedCommitAsync(request, scenesCreated, metricsCreated, ex); + await RecordFailedCommitAsync(request, 0, 0, ex); throw; } } @@ -591,4 +645,41 @@ public sealed class StoryIntelligenceResultRepository(ISqlConnectionFactory conn }, commandType: CommandType.StoredProcedure); } + + private static async Task TryOptionalSceneOperationAsync( + IDbConnection connection, + IDbTransaction transaction, + int sceneNumber, + string operation, + ICollection warnings, + Func> action) + { + try + { + await action(); + return true; + } + catch (Exception ex) + { + warnings.Add($"Scene {sceneNumber:N0}: skipped optional {operation} because the database rejected it: {ex.Message}"); + return false; + } + } + + private static string? TrimOptionalText(string? value, int maxLength, string label, ICollection warnings) + { + if (string.IsNullOrWhiteSpace(value)) + { + return null; + } + + var trimmed = value.Trim(); + if (trimmed.Length <= maxLength) + { + return trimmed; + } + + warnings.Add($"{label} was truncated to {maxLength:N0} characters for the database field limit."); + return trimmed[..maxLength].TrimEnd(); + } } diff --git a/PlotLine/Sql/124_Phase20Z2_StoryIntelligenceCommitAndProjectHardDelete.sql b/PlotLine/Sql/124_Phase20Z2_StoryIntelligenceCommitAndProjectHardDelete.sql new file mode 100644 index 0000000..f55f84a --- /dev/null +++ b/PlotLine/Sql/124_Phase20Z2_StoryIntelligenceCommitAndProjectHardDelete.sql @@ -0,0 +1,416 @@ +SET ANSI_NULLS ON; +GO +SET QUOTED_IDENTIFIER ON; +GO + +CREATE OR ALTER PROCEDURE dbo.ProjectHardDelete_ForOwner + @ProjectID int, + @UserID int +AS +BEGIN + SET NOCOUNT ON; + SET XACT_ABORT ON; + + IF NOT EXISTS + ( + SELECT 1 + FROM dbo.Projects p + INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID + WHERE p.ProjectID = @ProjectID + AND pua.UserID = @UserID + AND pua.AccessRole = N'Owner' + AND pua.IsActive = 1 + ) + THROW 52001, 'Project was not found for this owner.', 1; + + IF EXISTS (SELECT 1 FROM dbo.Projects WHERE ProjectID = @ProjectID AND IsArchived = 0) + THROW 52002, 'Only archived projects can be permanently deleted.', 1; + + BEGIN TRY + BEGIN TRANSACTION; + + DECLARE @BookIDs TABLE (BookID int NOT NULL PRIMARY KEY); + DECLARE @ChapterIDs TABLE (ChapterID int NOT NULL PRIMARY KEY); + DECLARE @SceneIDs TABLE (SceneID int NOT NULL PRIMARY KEY); + DECLARE @StoryIntelligenceRunIDs TABLE (StoryIntelligenceRunID int NOT NULL PRIMARY KEY); + DECLARE @StoryIntelligenceChapterResultIDs TABLE (ChapterResultID int NOT NULL PRIMARY KEY); + + INSERT @BookIDs (BookID) + SELECT BookID FROM dbo.Books WHERE ProjectID = @ProjectID; + + INSERT @ChapterIDs (ChapterID) + SELECT c.ChapterID + FROM dbo.Chapters c + INNER JOIN @BookIDs b ON b.BookID = c.BookID; + + INSERT @SceneIDs (SceneID) + SELECT s.SceneID + FROM dbo.Scenes s + INNER JOIN @ChapterIDs c ON c.ChapterID = s.ChapterID; + + INSERT @StoryIntelligenceRunIDs (StoryIntelligenceRunID) + SELECT DISTINCT r.StoryIntelligenceRunID + FROM dbo.StoryIntelligenceRuns r + WHERE r.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = r.BookID) + OR EXISTS (SELECT 1 FROM @ChapterIDs c WHERE c.ChapterID = r.ChapterID) + OR EXISTS (SELECT 1 FROM @SceneIDs s INNER JOIN dbo.Scenes scene ON scene.SceneID = s.SceneID WHERE scene.ImportRunID = r.StoryIntelligenceRunID); + + INSERT @StoryIntelligenceChapterResultIDs (ChapterResultID) + SELECT DISTINCT cr.ChapterResultID + FROM dbo.StoryIntelligenceChapterResults cr + WHERE cr.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @StoryIntelligenceRunIDs r WHERE r.StoryIntelligenceRunID = cr.StoryIntelligenceRunID) + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = cr.BookID) + OR EXISTS (SELECT 1 FROM @ChapterIDs c WHERE c.ChapterID = cr.ChapterID); + + DELETE sr + FROM dbo.StoryIntelligenceSceneResults sr + WHERE sr.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @StoryIntelligenceRunIDs r WHERE r.StoryIntelligenceRunID = sr.StoryIntelligenceRunID) + OR EXISTS (SELECT 1 FROM @StoryIntelligenceChapterResultIDs cr WHERE cr.ChapterResultID = sr.ChapterResultID) + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = sr.BookID) + OR EXISTS (SELECT 1 FROM @ChapterIDs c WHERE c.ChapterID = sr.ChapterID) + OR EXISTS (SELECT 1 FROM @SceneIDs s WHERE s.SceneID = sr.SceneID); + + DELETE cr + FROM dbo.StoryIntelligenceChapterResults cr + WHERE cr.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @StoryIntelligenceRunIDs r WHERE r.StoryIntelligenceRunID = cr.StoryIntelligenceRunID) + OR EXISTS (SELECT 1 FROM @StoryIntelligenceChapterResultIDs result WHERE result.ChapterResultID = cr.ChapterResultID) + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = cr.BookID) + OR EXISTS (SELECT 1 FROM @ChapterIDs c WHERE c.ChapterID = cr.ChapterID); + + DELETE commitRows + FROM dbo.StoryIntelligenceImportCommits commitRows + WHERE commitRows.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @StoryIntelligenceRunIDs r WHERE r.StoryIntelligenceRunID = commitRows.StoryIntelligenceRunID) + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = commitRows.BookID) + OR EXISTS (SELECT 1 FROM @ChapterIDs c WHERE c.ChapterID = commitRows.ChapterID); + + UPDATE scene + SET ImportRunID = NULL + FROM dbo.Scenes scene + INNER JOIN @SceneIDs s ON s.SceneID = scene.SceneID + WHERE scene.ImportRunID IS NOT NULL; + + DELETE r + FROM dbo.StoryIntelligenceRuns r + WHERE EXISTS (SELECT 1 FROM @StoryIntelligenceRunIDs runIDs WHERE runIDs.StoryIntelligenceRunID = r.StoryIntelligenceRunID); + + DELETE job + FROM dbo.StoryIntelligenceJobs job + LEFT JOIN dbo.StoryIntelligenceConsents consent ON consent.StoryIntelligenceConsentID = job.StoryIntelligenceConsentID + WHERE job.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = job.BookID) + OR consent.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = consent.BookID); + + DELETE consent + FROM dbo.StoryIntelligenceConsents consent + WHERE consent.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = consent.BookID); + + DELETE FROM dbo.ProjectActivity WHERE ProjectID = @ProjectID; + DELETE prh + FROM dbo.ProjectRestoreHistory prh + LEFT JOIN dbo.ProjectRestorePoints sourcePoint ON sourcePoint.ProjectRestorePointID = prh.SourceRestorePointID + LEFT JOIN dbo.ProjectRestorePoints safetyPoint ON safetyPoint.ProjectRestorePointID = prh.SafetyRestorePointID + WHERE prh.ProjectID = @ProjectID + OR sourcePoint.ProjectID = @ProjectID + OR safetyPoint.ProjectID = @ProjectID; + + DELETE FROM dbo.ProjectRestorePoints WHERE ProjectID = @ProjectID; + DELETE FROM dbo.ProjectInvitation WHERE ProjectID = @ProjectID; + + DELETE uf + FROM dbo.UserFile uf + WHERE uf.ProjectID = @ProjectID + OR EXISTS + ( + SELECT 1 + FROM dbo.SceneAttachments sa + INNER JOIN @SceneIDs s ON s.SceneID = sa.SceneID + WHERE sa.FilePath = uf.StoragePath + ); + + DELETE wsi + FROM dbo.WritingScheduleItems wsi + INNER JOIN dbo.WritingPlans wp ON wp.WritingPlanID = wsi.WritingPlanID + WHERE wp.ProjectID = @ProjectID; + + DELETE wsi + FROM dbo.WritingScheduleItems wsi + INNER JOIN @SceneIDs s ON s.SceneID = wsi.SceneID; + + DELETE FROM dbo.WritingPlans WHERE ProjectID = @ProjectID OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = dbo.WritingPlans.BookID); + + UPDATE dbo.UserOnboardingState + SET ProjectID = NULL, + BookID = NULL + WHERE ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = dbo.UserOnboardingState.BookID); + + DELETE ombsm FROM dbo.OnboardingManuscriptBuildSceneMap ombsm INNER JOIN @SceneIDs scene ON scene.SceneID = ombsm.SceneID; + DELETE ombcm FROM dbo.OnboardingManuscriptBuildChapterMap ombcm INNER JOIN @ChapterIDs chapter ON chapter.ChapterID = ombcm.ChapterID; + DELETE ombcm + FROM dbo.OnboardingManuscriptBuildChapterMap ombcm + INNER JOIN dbo.OnboardingManuscriptBuilds build ON build.OnboardingManuscriptBuildID = ombcm.OnboardingManuscriptBuildID + WHERE build.ProjectID = @ProjectID OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = build.BookID); + DELETE ombsm + FROM dbo.OnboardingManuscriptBuildSceneMap ombsm + INNER JOIN dbo.OnboardingManuscriptBuilds build ON build.OnboardingManuscriptBuildID = ombsm.OnboardingManuscriptBuildID + WHERE build.ProjectID = @ProjectID OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = build.BookID); + DELETE ombcharm + FROM dbo.OnboardingManuscriptBuildCharacterMap ombcharm + INNER JOIN dbo.OnboardingManuscriptBuilds build ON build.OnboardingManuscriptBuildID = ombcharm.OnboardingManuscriptBuildID + WHERE build.ProjectID = @ProjectID OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = build.BookID); + DELETE FROM dbo.OnboardingManuscriptBuilds WHERE ProjectID = @ProjectID OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = dbo.OnboardingManuscriptBuilds.BookID); + + DELETE sw + FROM dbo.ScenarioWarnings sw + INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = sw.ScenarioID + WHERE sc.ProjectID = @ProjectID; + + DELETE svr + FROM dbo.ScenarioValidationRuns svr + INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = svr.ScenarioID + WHERE sc.ProjectID = @ProjectID; + + DELETE sso + FROM dbo.ScenarioSceneOrder sso + INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = sso.ScenarioID + WHERE sc.ProjectID = @ProjectID; + + DELETE sso + FROM dbo.ScenarioSceneOrder sso + WHERE EXISTS (SELECT 1 FROM @SceneIDs scene WHERE scene.SceneID = sso.SceneID) + OR EXISTS (SELECT 1 FROM @ChapterIDs chapter WHERE chapter.ChapterID = sso.ProposedChapterID); + + DELETE FROM dbo.Scenarios WHERE ProjectID = @ProjectID; + + DELETE cwa + FROM dbo.ContinuityWarningAcknowledgements cwa + WHERE cwa.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @SceneIDs scene WHERE scene.SceneID = cwa.SceneID); + + DELETE FROM dbo.ContinuityWarnings WHERE ProjectID = @ProjectID; + DELETE FROM dbo.ValidationRuns WHERE ProjectID = @ProjectID; + + DELETE sfpo FROM dbo.SceneFloorPlanOccupancy sfpo INNER JOIN @SceneIDs s ON s.SceneID = sfpo.SceneID; + DELETE sa FROM dbo.SceneAttachments sa INNER JOIN @SceneIDs s ON s.SceneID = sa.SceneID; + DELETE sci FROM dbo.SceneChecklistItems sci INNER JOIN @SceneIDs s ON s.SceneID = sci.SceneID; + DELETE sw FROM dbo.SceneWorkflow sw INNER JOIN @SceneIDs s ON s.SceneID = sw.SceneID; + DELETE sn FROM dbo.SceneNotes sn INNER JOIN @SceneIDs s ON s.SceneID = sn.SceneID; + DELETE cg FROM dbo.ChapterGoals cg INNER JOIN @ChapterIDs c ON c.ChapterID = cg.ChapterID; + + DELETE sd + FROM dbo.SceneDependencies sd + LEFT JOIN @SceneIDs ss ON ss.SceneID = sd.SourceSceneID + LEFT JOIN @SceneIDs ts ON ts.SceneID = sd.TargetSceneID + WHERE sd.ProjectID = @ProjectID + OR ss.SceneID IS NOT NULL + OR ts.SceneID IS NOT NULL; + + DELETE re + FROM dbo.RelationshipEvents re + INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID + WHERE cr.ProjectID = @ProjectID; + + DELETE re FROM dbo.RelationshipEvents re INNER JOIN @SceneIDs scene ON scene.SceneID = re.SceneID; + DELETE FROM dbo.CharacterRelationships WHERE ProjectID = @ProjectID; + + DELETE ck + FROM dbo.CharacterKnowledge ck + LEFT JOIN dbo.Characters ch ON ch.CharacterID = ck.CharacterID + LEFT JOIN @SceneIDs scene ON scene.SceneID = ck.SceneID + LEFT JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ck.StoryAssetID + LEFT JOIN dbo.PlotThreads pt ON pt.PlotThreadID = ck.PlotThreadID + LEFT JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID + WHERE ch.ProjectID = @ProjectID + OR scene.SceneID IS NOT NULL + OR sa.ProjectID = @ProjectID + OR pl.ProjectID = @ProjectID; + + DELETE cae + FROM dbo.CharacterAttributeEvents cae + LEFT JOIN dbo.Characters ch ON ch.CharacterID = cae.CharacterID + LEFT JOIN @SceneIDs scene ON scene.SceneID = cae.SceneID + WHERE ch.ProjectID = @ProjectID + OR scene.SceneID IS NOT NULL; + + DELETE sc + FROM dbo.SceneCharacters sc + LEFT JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID + LEFT JOIN @SceneIDs scene ON scene.SceneID = sc.SceneID + WHERE ch.ProjectID = @ProjectID + OR scene.SceneID IS NOT NULL; + + DELETE acec + FROM dbo.AssetCustodyEventCharacters acec + INNER JOIN dbo.AssetCustodyEvents ace ON ace.AssetCustodyEventID = acec.AssetCustodyEventID + INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID + WHERE sa.ProjectID = @ProjectID; + + DELETE acec + FROM dbo.AssetCustodyEventCharacters acec + INNER JOIN dbo.AssetCustodyEvents ace ON ace.AssetCustodyEventID = acec.AssetCustodyEventID + INNER JOIN @SceneIDs scene ON scene.SceneID = ace.SceneID; + + DELETE ace + FROM dbo.AssetCustodyEvents ace + LEFT JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID + LEFT JOIN @SceneIDs scene ON scene.SceneID = ace.SceneID + WHERE sa.ProjectID = @ProjectID + OR scene.SceneID IS NOT NULL; + + DELETE ad + FROM dbo.AssetDependencies ad + INNER JOIN dbo.StoryAssets sourceAsset ON sourceAsset.StoryAssetID = ad.SourceAssetID + INNER JOIN dbo.StoryAssets targetAsset ON targetAsset.StoryAssetID = ad.TargetAssetID + WHERE sourceAsset.ProjectID = @ProjectID + OR targetAsset.ProjectID = @ProjectID; + + DELETE ae + FROM dbo.AssetEvents ae + LEFT JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID + LEFT JOIN @SceneIDs scene ON scene.SceneID = ae.SceneID + WHERE sa.ProjectID = @ProjectID + OR scene.SceneID IS NOT NULL; + + DELETE sal + FROM dbo.SceneAssetLocations sal + LEFT JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID + LEFT JOIN @SceneIDs scene ON scene.SceneID = sal.SceneID + WHERE sa.ProjectID = @ProjectID + OR scene.SceneID IS NOT NULL; + + DELETE petpl + FROM dbo.PlotEventTargetPlotLines petpl + LEFT JOIN dbo.ThreadEvents te ON te.ThreadEventID = petpl.PlotEventID + LEFT JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID + LEFT JOIN dbo.PlotLines eventPlotLine ON eventPlotLine.PlotLineID = pt.PlotLineID + LEFT JOIN dbo.PlotLines targetPlotLine ON targetPlotLine.PlotLineID = petpl.PlotLineID + WHERE eventPlotLine.ProjectID = @ProjectID + OR targetPlotLine.ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @SceneIDs scene WHERE scene.SceneID = te.SceneID); + + DELETE te + FROM dbo.ThreadEvents te + LEFT JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID + LEFT JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID + LEFT JOIN dbo.PlotLines target ON target.PlotLineID = te.TargetPlotLineID + LEFT JOIN @SceneIDs scene ON scene.SceneID = te.SceneID + WHERE pl.ProjectID = @ProjectID + OR target.ProjectID = @ProjectID + OR scene.SceneID IS NOT NULL; + + DELETE pt + FROM dbo.PlotThreads pt + INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID + WHERE pl.ProjectID = @ProjectID; + + UPDATE dbo.PlotLines + SET ParentPlotLineID = NULL, + EmergesFromPlotLineID = NULL + WHERE ProjectID = @ProjectID; + + DELETE FROM dbo.PlotLines WHERE ProjectID = @ProjectID; + + DELETE FROM dbo.TimelineViewPresets WHERE ProjectID = @ProjectID OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = dbo.TimelineViewPresets.BookID); + DELETE FROM dbo.ProjectTimelineMetricSettings WHERE ProjectID = @ProjectID; + DELETE FROM dbo.ProjectTimelineSettings WHERE ProjectID = @ProjectID; + + DELETE smv FROM dbo.SceneMetricValues smv INNER JOIN @SceneIDs s ON s.SceneID = smv.SceneID; + DELETE sp FROM dbo.ScenePurposes sp INNER JOIN @SceneIDs s ON s.SceneID = sp.SceneID; + DELETE so FROM dbo.SceneOutcomes so INNER JOIN @SceneIDs s ON s.SceneID = so.SceneID; + + UPDATE dbo.Characters + SET AgeReferenceSceneID = NULL + WHERE ProjectID = @ProjectID; + + UPDATE s + SET PrimaryLocationID = NULL, + FloorPlanID = NULL, + InitialFloorPlanFloorID = NULL, + ManuscriptDocumentID = NULL + FROM dbo.Scenes s + INNER JOIN @SceneIDs scene ON scene.SceneID = s.SceneID; + + UPDATE c + SET ManuscriptDocumentID = NULL + FROM dbo.Chapters c + INNER JOIN @ChapterIDs chapter ON chapter.ChapterID = c.ChapterID; + + UPDATE dbo.StoryAssets + SET CurrentLocationID = NULL + WHERE ProjectID = @ProjectID; + + DELETE fpt + FROM dbo.FloorPlanTransitions fpt + INNER JOIN dbo.FloorPlanFloors fpf ON fpf.FloorPlanFloorID = fpt.FloorPlanFloorID + INNER JOIN dbo.FloorPlans fp ON fp.FloorPlanID = fpf.FloorPlanID + WHERE fp.ProjectID = @ProjectID; + + DELETE fpb + FROM dbo.FloorPlanBlocks fpb + INNER JOIN dbo.FloorPlanFloors fpf ON fpf.FloorPlanFloorID = fpb.FloorPlanFloorID + INNER JOIN dbo.FloorPlans fp ON fp.FloorPlanID = fpf.FloorPlanID + WHERE fp.ProjectID = @ProjectID; + + DELETE fpf + FROM dbo.FloorPlanFloors fpf + INNER JOIN dbo.FloorPlans fp ON fp.FloorPlanID = fpf.FloorPlanID + WHERE fp.ProjectID = @ProjectID; + + DELETE FROM dbo.FloorPlans WHERE ProjectID = @ProjectID; + + DELETE FROM dbo.StoryAssets WHERE ProjectID = @ProjectID; + + UPDATE dbo.Locations + SET ParentLocationID = NULL + WHERE ProjectID = @ProjectID; + + DELETE lr + FROM dbo.LocationRelationships lr + INNER JOIN dbo.Locations fromLocation ON fromLocation.LocationID = lr.FromLocationID + INNER JOIN dbo.Locations toLocation ON toLocation.LocationID = lr.ToLocationID + WHERE fromLocation.ProjectID = @ProjectID + OR toLocation.ProjectID = @ProjectID; + + DELETE FROM dbo.Locations WHERE ProjectID = @ProjectID; + + DELETE s FROM dbo.Scenes s INNER JOIN @SceneIDs scene ON scene.SceneID = s.SceneID; + DELETE c FROM dbo.Chapters c INNER JOIN @ChapterIDs chapter ON chapter.ChapterID = c.ChapterID; + + DELETE FROM dbo.ManuscriptDocuments + WHERE ProjectID = @ProjectID + OR EXISTS (SELECT 1 FROM @BookIDs b WHERE b.BookID = dbo.ManuscriptDocuments.BookID); + + DELETE FROM dbo.Books WHERE ProjectID = @ProjectID; + + DELETE FROM dbo.CharacterImages WHERE ProjectID = @ProjectID; + DELETE FROM dbo.Characters WHERE ProjectID = @ProjectID; + + DELETE gpmm + FROM dbo.GenreMetricPresetMetrics gpmm + INNER JOIN dbo.SceneMetricTypes mt ON mt.MetricTypeID = gpmm.MetricTypeID + WHERE mt.ProjectID = @ProjectID; + + DELETE FROM dbo.SceneMetricTypes WHERE ProjectID = @ProjectID; + DELETE FROM dbo.CharacterAttributeTypes WHERE ProjectID = @ProjectID; + DELETE FROM dbo.AssetStates WHERE ProjectID = @ProjectID; + + DELETE FROM dbo.ProjectUserAccess WHERE ProjectID = @ProjectID; + DELETE FROM dbo.Projects WHERE ProjectID = @ProjectID; + + COMMIT TRANSACTION; + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK TRANSACTION; + + THROW; + END CATCH +END; +GO