Phase 9B is implemented: database foundation only, no schedule generation or UI.
This commit is contained in:
parent
b2cfcd08fa
commit
e66b43064a
@ -272,6 +272,22 @@ public interface IWriterWorkspaceRepository
|
||||
Task<IReadOnlyList<StoryBibleAttentionItem>> GetWriterDashboardRemindersAsync(int? projectId);
|
||||
}
|
||||
|
||||
public interface IWritingScheduleRepository
|
||||
{
|
||||
Task<IReadOnlyList<WritingPlan>> GetWritingPlansByUserAsync(int userId);
|
||||
Task<WritingPlan?> GetWritingPlanByIDAsync(int writingPlanId);
|
||||
Task<int> CreateWritingPlanAsync(WritingPlan plan);
|
||||
Task UpdateWritingPlanAsync(WritingPlan plan);
|
||||
Task DeleteWritingPlanAsync(int writingPlanId);
|
||||
Task SetWritingPlanActiveAsync(int writingPlanId, bool isActive);
|
||||
Task<IReadOnlyList<WritingScheduleItem>> GetScheduleItemsByPlanAsync(int writingPlanId);
|
||||
Task<IReadOnlyList<WritingScheduleItem>> GetScheduleItemsByDateRangeAsync(int writingPlanId, DateTime startDate, DateTime endDate);
|
||||
Task<int> CreateScheduleItemAsync(WritingScheduleItem item);
|
||||
Task UpdateScheduleItemAsync(WritingScheduleItem item);
|
||||
Task DeleteScheduleItemAsync(int writingScheduleItemId);
|
||||
Task UpdateScheduleItemStatusAsync(int writingScheduleItemId, string scheduleStatus, DateTime? completedDateUtc);
|
||||
}
|
||||
|
||||
public interface IExportRepository
|
||||
{
|
||||
Task<ExportPacket> GetStoryBibleAsync(int projectId);
|
||||
@ -842,6 +858,154 @@ public sealed class WriterWorkspaceRepository(ISqlConnectionFactory connectionFa
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WritingScheduleRepository(ISqlConnectionFactory connectionFactory) : IWritingScheduleRepository
|
||||
{
|
||||
public async Task<IReadOnlyList<WritingPlan>> GetWritingPlansByUserAsync(int userId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var rows = await connection.QueryAsync<WritingPlan>("dbo.GetWritingPlansByUser", new { UserID = userId }, commandType: CommandType.StoredProcedure);
|
||||
return rows.ToList();
|
||||
}
|
||||
|
||||
public async Task<WritingPlan?> GetWritingPlanByIDAsync(int writingPlanId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
return await connection.QuerySingleOrDefaultAsync<WritingPlan>("dbo.GetWritingPlanByID", new { WritingPlanID = writingPlanId }, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<int> CreateWritingPlanAsync(WritingPlan plan)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
return await connection.QuerySingleAsync<int>(
|
||||
"dbo.CreateWritingPlan",
|
||||
new
|
||||
{
|
||||
plan.UserID,
|
||||
plan.ProjectID,
|
||||
plan.BookID,
|
||||
plan.PlanName,
|
||||
plan.GoalType,
|
||||
plan.StartDate,
|
||||
plan.DeadlineDate,
|
||||
plan.AvailableDaysJson,
|
||||
plan.SessionLengthMinutes,
|
||||
plan.IncludeBlockedScenes,
|
||||
plan.RebalanceMode,
|
||||
plan.IsActive
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task UpdateWritingPlanAsync(WritingPlan plan)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync(
|
||||
"dbo.UpdateWritingPlan",
|
||||
new
|
||||
{
|
||||
plan.WritingPlanID,
|
||||
plan.UserID,
|
||||
plan.ProjectID,
|
||||
plan.BookID,
|
||||
plan.PlanName,
|
||||
plan.GoalType,
|
||||
plan.StartDate,
|
||||
plan.DeadlineDate,
|
||||
plan.AvailableDaysJson,
|
||||
plan.SessionLengthMinutes,
|
||||
plan.IncludeBlockedScenes,
|
||||
plan.RebalanceMode,
|
||||
plan.IsActive
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task DeleteWritingPlanAsync(int writingPlanId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync("dbo.DeleteWritingPlan", new { WritingPlanID = writingPlanId }, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task SetWritingPlanActiveAsync(int writingPlanId, bool isActive)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync("dbo.SetWritingPlanActive", new { WritingPlanID = writingPlanId, IsActive = isActive }, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<WritingScheduleItem>> GetScheduleItemsByPlanAsync(int writingPlanId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var rows = await connection.QueryAsync<WritingScheduleItem>("dbo.GetScheduleItemsByPlan", new { WritingPlanID = writingPlanId }, commandType: CommandType.StoredProcedure);
|
||||
return rows.ToList();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<WritingScheduleItem>> GetScheduleItemsByDateRangeAsync(int writingPlanId, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var rows = await connection.QueryAsync<WritingScheduleItem>(
|
||||
"dbo.GetScheduleItemsByDateRange",
|
||||
new { WritingPlanID = writingPlanId, StartDate = startDate, EndDate = endDate },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
return rows.ToList();
|
||||
}
|
||||
|
||||
public async Task<int> CreateScheduleItemAsync(WritingScheduleItem item)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
return await connection.QuerySingleAsync<int>(
|
||||
"dbo.CreateScheduleItem",
|
||||
new
|
||||
{
|
||||
item.WritingPlanID,
|
||||
item.SceneID,
|
||||
item.ScheduledDate,
|
||||
item.TaskType,
|
||||
item.EstimatedMinutes,
|
||||
item.TargetWords,
|
||||
item.ScheduleStatus,
|
||||
item.CompletedDateUtc,
|
||||
item.Notes
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task UpdateScheduleItemAsync(WritingScheduleItem item)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync(
|
||||
"dbo.UpdateScheduleItem",
|
||||
new
|
||||
{
|
||||
item.WritingScheduleItemID,
|
||||
item.WritingPlanID,
|
||||
item.SceneID,
|
||||
item.ScheduledDate,
|
||||
item.TaskType,
|
||||
item.EstimatedMinutes,
|
||||
item.TargetWords,
|
||||
item.ScheduleStatus,
|
||||
item.CompletedDateUtc,
|
||||
item.Notes
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task DeleteScheduleItemAsync(int writingScheduleItemId)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync("dbo.DeleteScheduleItem", new { WritingScheduleItemID = writingScheduleItemId }, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task UpdateScheduleItemStatusAsync(int writingScheduleItemId, string scheduleStatus, DateTime? completedDateUtc)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
await connection.ExecuteAsync(
|
||||
"dbo.UpdateScheduleItemStatus",
|
||||
new { WritingScheduleItemID = writingScheduleItemId, ScheduleStatus = scheduleStatus, CompletedDateUtc = completedDateUtc },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ExportRepository(ISqlConnectionFactory connectionFactory) : IExportRepository
|
||||
{
|
||||
public async Task<ExportPacket> GetStoryBibleAsync(int projectId)
|
||||
|
||||
@ -1572,6 +1572,41 @@ public sealed class WriterDashboardScene
|
||||
public string? PinnedNotes { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WritingPlan
|
||||
{
|
||||
public int WritingPlanID { get; set; }
|
||||
public int UserID { get; set; }
|
||||
public int ProjectID { get; set; }
|
||||
public int? BookID { get; set; }
|
||||
public string PlanName { get; set; } = string.Empty;
|
||||
public string GoalType { get; set; } = string.Empty;
|
||||
public DateTime StartDate { get; set; }
|
||||
public DateTime DeadlineDate { get; set; }
|
||||
public string AvailableDaysJson { get; set; } = string.Empty;
|
||||
public int SessionLengthMinutes { get; set; }
|
||||
public bool IncludeBlockedScenes { get; set; }
|
||||
public string RebalanceMode { get; set; } = "Ask";
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime CreatedDateUtc { get; set; }
|
||||
public DateTime ModifiedDateUtc { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WritingScheduleItem
|
||||
{
|
||||
public int WritingScheduleItemID { get; set; }
|
||||
public int WritingPlanID { get; set; }
|
||||
public int SceneID { get; set; }
|
||||
public DateTime ScheduledDate { get; set; }
|
||||
public string TaskType { get; set; } = string.Empty;
|
||||
public int? EstimatedMinutes { get; set; }
|
||||
public int? TargetWords { get; set; }
|
||||
public string ScheduleStatus { get; set; } = string.Empty;
|
||||
public DateTime? CompletedDateUtc { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public DateTime CreatedDateUtc { get; set; }
|
||||
public DateTime ModifiedDateUtc { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ChapterWorkflowScene
|
||||
{
|
||||
public int SceneID { get; set; }
|
||||
|
||||
@ -96,6 +96,7 @@ public class Program
|
||||
builder.Services.AddScoped<IAnalyticsRepository, AnalyticsRepository>();
|
||||
builder.Services.AddScoped<IStoryBibleRepository, StoryBibleRepository>();
|
||||
builder.Services.AddScoped<IWriterWorkspaceRepository, WriterWorkspaceRepository>();
|
||||
builder.Services.AddScoped<IWritingScheduleRepository, WritingScheduleRepository>();
|
||||
builder.Services.AddScoped<IExportRepository, ExportRepository>();
|
||||
builder.Services.AddScoped<IProjectBackupRepository, ProjectBackupRepository>();
|
||||
builder.Services.AddScoped<IProjectRestorePointRepository, ProjectRestorePointRepository>();
|
||||
@ -127,6 +128,7 @@ public class Program
|
||||
builder.Services.AddScoped<IStoryBibleService, StoryBibleService>();
|
||||
builder.Services.AddScoped<IRelationshipMapService, RelationshipMapService>();
|
||||
builder.Services.AddScoped<IWriterWorkspaceService, WriterWorkspaceService>();
|
||||
builder.Services.AddScoped<IWritingScheduleService, WritingScheduleService>();
|
||||
builder.Services.AddScoped<IStorageService, StorageService>();
|
||||
builder.Services.AddScoped<IExportService, ExportService>();
|
||||
builder.Services.AddScoped<IProjectExportService, ProjectExportService>();
|
||||
|
||||
@ -256,6 +256,22 @@ public interface IWriterWorkspaceService
|
||||
Task DeleteAttachmentAsync(int sceneAttachmentId);
|
||||
}
|
||||
|
||||
public interface IWritingScheduleService
|
||||
{
|
||||
Task<IReadOnlyList<WritingPlanViewModel>> GetWritingPlansByUserAsync(int userId);
|
||||
Task<WritingPlanViewModel?> GetWritingPlanByIDAsync(int writingPlanId);
|
||||
Task<int> CreateWritingPlanAsync(WritingPlanViewModel model);
|
||||
Task UpdateWritingPlanAsync(WritingPlanViewModel model);
|
||||
Task DeleteWritingPlanAsync(int writingPlanId);
|
||||
Task SetWritingPlanActiveAsync(int writingPlanId, bool isActive);
|
||||
Task<IReadOnlyList<WritingScheduleItemViewModel>> GetScheduleItemsByPlanAsync(int writingPlanId);
|
||||
Task<IReadOnlyList<WritingScheduleItemViewModel>> GetScheduleItemsByDateRangeAsync(int writingPlanId, DateTime startDate, DateTime endDate);
|
||||
Task<int> CreateScheduleItemAsync(WritingScheduleItemViewModel model);
|
||||
Task UpdateScheduleItemAsync(WritingScheduleItemViewModel model);
|
||||
Task DeleteScheduleItemAsync(int writingScheduleItemId);
|
||||
Task UpdateScheduleItemStatusAsync(int writingScheduleItemId, string scheduleStatus, DateTime? completedDateUtc);
|
||||
}
|
||||
|
||||
public interface IStorageService
|
||||
{
|
||||
Task<StorageUsage?> GetSceneStorageUsageAsync(int sceneId);
|
||||
@ -3259,6 +3275,165 @@ public sealed class WriterWorkspaceService(
|
||||
public Task DeleteAttachmentAsync(int sceneAttachmentId) => writerWorkspace.DeleteAttachmentAsync(sceneAttachmentId);
|
||||
}
|
||||
|
||||
public sealed class WritingScheduleService(IWritingScheduleRepository writingSchedule) : IWritingScheduleService
|
||||
{
|
||||
public async Task<IReadOnlyList<WritingPlanViewModel>> GetWritingPlansByUserAsync(int userId)
|
||||
{
|
||||
var plans = await writingSchedule.GetWritingPlansByUserAsync(userId);
|
||||
return plans.Select(ToViewModel).ToList();
|
||||
}
|
||||
|
||||
public async Task<WritingPlanViewModel?> GetWritingPlanByIDAsync(int writingPlanId)
|
||||
{
|
||||
var plan = await writingSchedule.GetWritingPlanByIDAsync(writingPlanId);
|
||||
return plan is null ? null : ToViewModel(plan);
|
||||
}
|
||||
|
||||
public Task<int> CreateWritingPlanAsync(WritingPlanViewModel model)
|
||||
{
|
||||
ValidatePlan(model);
|
||||
return writingSchedule.CreateWritingPlanAsync(ToModel(model));
|
||||
}
|
||||
|
||||
public Task UpdateWritingPlanAsync(WritingPlanViewModel model)
|
||||
{
|
||||
ValidatePlan(model);
|
||||
return writingSchedule.UpdateWritingPlanAsync(ToModel(model));
|
||||
}
|
||||
|
||||
public Task DeleteWritingPlanAsync(int writingPlanId) =>
|
||||
writingSchedule.DeleteWritingPlanAsync(writingPlanId);
|
||||
|
||||
public Task SetWritingPlanActiveAsync(int writingPlanId, bool isActive) =>
|
||||
writingSchedule.SetWritingPlanActiveAsync(writingPlanId, isActive);
|
||||
|
||||
public async Task<IReadOnlyList<WritingScheduleItemViewModel>> GetScheduleItemsByPlanAsync(int writingPlanId)
|
||||
{
|
||||
var items = await writingSchedule.GetScheduleItemsByPlanAsync(writingPlanId);
|
||||
return items.Select(ToViewModel).ToList();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<WritingScheduleItemViewModel>> GetScheduleItemsByDateRangeAsync(int writingPlanId, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
var items = await writingSchedule.GetScheduleItemsByDateRangeAsync(writingPlanId, startDate, endDate);
|
||||
return items.Select(ToViewModel).ToList();
|
||||
}
|
||||
|
||||
public Task<int> CreateScheduleItemAsync(WritingScheduleItemViewModel model)
|
||||
{
|
||||
ValidateScheduleItem(model);
|
||||
return writingSchedule.CreateScheduleItemAsync(ToModel(model));
|
||||
}
|
||||
|
||||
public Task UpdateScheduleItemAsync(WritingScheduleItemViewModel model)
|
||||
{
|
||||
ValidateScheduleItem(model);
|
||||
return writingSchedule.UpdateScheduleItemAsync(ToModel(model));
|
||||
}
|
||||
|
||||
public Task DeleteScheduleItemAsync(int writingScheduleItemId) =>
|
||||
writingSchedule.DeleteScheduleItemAsync(writingScheduleItemId);
|
||||
|
||||
public Task UpdateScheduleItemStatusAsync(int writingScheduleItemId, string scheduleStatus, DateTime? completedDateUtc) =>
|
||||
writingSchedule.UpdateScheduleItemStatusAsync(writingScheduleItemId, scheduleStatus, completedDateUtc);
|
||||
|
||||
private static void ValidatePlan(WritingPlanViewModel model)
|
||||
{
|
||||
if (model.DeadlineDate.Date < model.StartDate.Date)
|
||||
{
|
||||
throw new InvalidOperationException("Deadline date must be on or after the start date.");
|
||||
}
|
||||
|
||||
if (model.SessionLengthMinutes <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Session length must be greater than zero.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateScheduleItem(WritingScheduleItemViewModel model)
|
||||
{
|
||||
if (model.TargetWords < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Target words cannot be negative.");
|
||||
}
|
||||
|
||||
if (model.EstimatedMinutes < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Estimated minutes cannot be negative.");
|
||||
}
|
||||
}
|
||||
|
||||
private static WritingPlanViewModel ToViewModel(WritingPlan plan) => new()
|
||||
{
|
||||
WritingPlanID = plan.WritingPlanID,
|
||||
UserID = plan.UserID,
|
||||
ProjectID = plan.ProjectID,
|
||||
BookID = plan.BookID,
|
||||
PlanName = plan.PlanName,
|
||||
GoalType = plan.GoalType,
|
||||
StartDate = plan.StartDate,
|
||||
DeadlineDate = plan.DeadlineDate,
|
||||
AvailableDaysJson = plan.AvailableDaysJson,
|
||||
SessionLengthMinutes = plan.SessionLengthMinutes,
|
||||
IncludeBlockedScenes = plan.IncludeBlockedScenes,
|
||||
RebalanceMode = plan.RebalanceMode,
|
||||
IsActive = plan.IsActive,
|
||||
CreatedDateUtc = plan.CreatedDateUtc,
|
||||
ModifiedDateUtc = plan.ModifiedDateUtc
|
||||
};
|
||||
|
||||
private static WritingPlan ToModel(WritingPlanViewModel model) => new()
|
||||
{
|
||||
WritingPlanID = model.WritingPlanID,
|
||||
UserID = model.UserID,
|
||||
ProjectID = model.ProjectID,
|
||||
BookID = model.BookID,
|
||||
PlanName = model.PlanName,
|
||||
GoalType = model.GoalType,
|
||||
StartDate = model.StartDate,
|
||||
DeadlineDate = model.DeadlineDate,
|
||||
AvailableDaysJson = model.AvailableDaysJson,
|
||||
SessionLengthMinutes = model.SessionLengthMinutes,
|
||||
IncludeBlockedScenes = model.IncludeBlockedScenes,
|
||||
RebalanceMode = model.RebalanceMode,
|
||||
IsActive = model.IsActive,
|
||||
CreatedDateUtc = model.CreatedDateUtc,
|
||||
ModifiedDateUtc = model.ModifiedDateUtc
|
||||
};
|
||||
|
||||
private static WritingScheduleItemViewModel ToViewModel(WritingScheduleItem item) => new()
|
||||
{
|
||||
WritingScheduleItemID = item.WritingScheduleItemID,
|
||||
WritingPlanID = item.WritingPlanID,
|
||||
SceneID = item.SceneID,
|
||||
ScheduledDate = item.ScheduledDate,
|
||||
TaskType = item.TaskType,
|
||||
EstimatedMinutes = item.EstimatedMinutes,
|
||||
TargetWords = item.TargetWords,
|
||||
ScheduleStatus = item.ScheduleStatus,
|
||||
CompletedDateUtc = item.CompletedDateUtc,
|
||||
Notes = item.Notes,
|
||||
CreatedDateUtc = item.CreatedDateUtc,
|
||||
ModifiedDateUtc = item.ModifiedDateUtc
|
||||
};
|
||||
|
||||
private static WritingScheduleItem ToModel(WritingScheduleItemViewModel model) => new()
|
||||
{
|
||||
WritingScheduleItemID = model.WritingScheduleItemID,
|
||||
WritingPlanID = model.WritingPlanID,
|
||||
SceneID = model.SceneID,
|
||||
ScheduledDate = model.ScheduledDate,
|
||||
TaskType = model.TaskType,
|
||||
EstimatedMinutes = model.EstimatedMinutes,
|
||||
TargetWords = model.TargetWords,
|
||||
ScheduleStatus = model.ScheduleStatus,
|
||||
CompletedDateUtc = model.CompletedDateUtc,
|
||||
Notes = model.Notes,
|
||||
CreatedDateUtc = model.CreatedDateUtc,
|
||||
ModifiedDateUtc = model.ModifiedDateUtc
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class ExportService(IExportRepository exports) : IExportService
|
||||
{
|
||||
public async Task<(string FileName, string ContentType, string Content)> ExportStoryBibleAsync(int projectId, string format)
|
||||
|
||||
323
PlotLine/Sql/056_Phase9B_WritingScheduleFoundation.sql
Normal file
323
PlotLine/Sql/056_Phase9B_WritingScheduleFoundation.sql
Normal file
@ -0,0 +1,323 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.WritingPlans', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.WritingPlans
|
||||
(
|
||||
WritingPlanID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_WritingPlans PRIMARY KEY,
|
||||
UserID int NOT NULL,
|
||||
ProjectID int NOT NULL,
|
||||
BookID int NULL,
|
||||
PlanName nvarchar(200) NOT NULL,
|
||||
GoalType nvarchar(50) NOT NULL,
|
||||
StartDate date NOT NULL,
|
||||
DeadlineDate date NOT NULL,
|
||||
AvailableDaysJson nvarchar(max) NOT NULL,
|
||||
SessionLengthMinutes int NOT NULL,
|
||||
IncludeBlockedScenes bit NOT NULL CONSTRAINT DF_WritingPlans_IncludeBlockedScenes DEFAULT 0,
|
||||
RebalanceMode nvarchar(50) NOT NULL CONSTRAINT DF_WritingPlans_RebalanceMode DEFAULT N'Ask',
|
||||
IsActive bit NOT NULL CONSTRAINT DF_WritingPlans_IsActive DEFAULT 1,
|
||||
CreatedDateUtc datetime2 NOT NULL CONSTRAINT DF_WritingPlans_CreatedDateUtc DEFAULT GETUTCDATE(),
|
||||
ModifiedDateUtc datetime2 NOT NULL CONSTRAINT DF_WritingPlans_ModifiedDateUtc DEFAULT GETUTCDATE(),
|
||||
CONSTRAINT FK_WritingPlans_AppUser FOREIGN KEY (UserID) REFERENCES dbo.AppUser(UserID),
|
||||
CONSTRAINT FK_WritingPlans_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
|
||||
CONSTRAINT FK_WritingPlans_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID)
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.WritingScheduleItems', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.WritingScheduleItems
|
||||
(
|
||||
WritingScheduleItemID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_WritingScheduleItems PRIMARY KEY,
|
||||
WritingPlanID int NOT NULL,
|
||||
SceneID int NOT NULL,
|
||||
ScheduledDate date NOT NULL,
|
||||
TaskType nvarchar(50) NOT NULL,
|
||||
EstimatedMinutes int NULL,
|
||||
TargetWords int NULL,
|
||||
ScheduleStatus nvarchar(50) NOT NULL,
|
||||
CompletedDateUtc datetime2 NULL,
|
||||
Notes nvarchar(1000) NULL,
|
||||
CreatedDateUtc datetime2 NOT NULL CONSTRAINT DF_WritingScheduleItems_CreatedDateUtc DEFAULT GETUTCDATE(),
|
||||
ModifiedDateUtc datetime2 NOT NULL CONSTRAINT DF_WritingScheduleItems_ModifiedDateUtc DEFAULT GETUTCDATE(),
|
||||
CONSTRAINT FK_WritingScheduleItems_WritingPlans FOREIGN KEY (WritingPlanID) REFERENCES dbo.WritingPlans(WritingPlanID),
|
||||
CONSTRAINT FK_WritingScheduleItems_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID)
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_WritingPlans_UserID' AND object_id = OBJECT_ID(N'dbo.WritingPlans'))
|
||||
CREATE INDEX IX_WritingPlans_UserID ON dbo.WritingPlans(UserID);
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_WritingPlans_ProjectID' AND object_id = OBJECT_ID(N'dbo.WritingPlans'))
|
||||
CREATE INDEX IX_WritingPlans_ProjectID ON dbo.WritingPlans(ProjectID);
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_WritingPlans_IsActive' AND object_id = OBJECT_ID(N'dbo.WritingPlans'))
|
||||
CREATE INDEX IX_WritingPlans_IsActive ON dbo.WritingPlans(IsActive);
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_WritingScheduleItems_WritingPlanID' AND object_id = OBJECT_ID(N'dbo.WritingScheduleItems'))
|
||||
CREATE INDEX IX_WritingScheduleItems_WritingPlanID ON dbo.WritingScheduleItems(WritingPlanID);
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_WritingScheduleItems_SceneID' AND object_id = OBJECT_ID(N'dbo.WritingScheduleItems'))
|
||||
CREATE INDEX IX_WritingScheduleItems_SceneID ON dbo.WritingScheduleItems(SceneID);
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_WritingScheduleItems_ScheduledDate' AND object_id = OBJECT_ID(N'dbo.WritingScheduleItems'))
|
||||
CREATE INDEX IX_WritingScheduleItems_ScheduledDate ON dbo.WritingScheduleItems(ScheduledDate);
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_WritingScheduleItems_ScheduleStatus' AND object_id = OBJECT_ID(N'dbo.WritingScheduleItems'))
|
||||
CREATE INDEX IX_WritingScheduleItems_ScheduleStatus ON dbo.WritingScheduleItems(ScheduleStatus);
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.GetWritingPlansByUser
|
||||
@UserID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT WritingPlanID, UserID, ProjectID, BookID, PlanName, GoalType, StartDate, DeadlineDate,
|
||||
AvailableDaysJson, SessionLengthMinutes, IncludeBlockedScenes, RebalanceMode, IsActive,
|
||||
CreatedDateUtc, ModifiedDateUtc
|
||||
FROM dbo.WritingPlans
|
||||
WHERE UserID = @UserID
|
||||
ORDER BY IsActive DESC, DeadlineDate, StartDate, WritingPlanID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.GetWritingPlanByID
|
||||
@WritingPlanID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT WritingPlanID, UserID, ProjectID, BookID, PlanName, GoalType, StartDate, DeadlineDate,
|
||||
AvailableDaysJson, SessionLengthMinutes, IncludeBlockedScenes, RebalanceMode, IsActive,
|
||||
CreatedDateUtc, ModifiedDateUtc
|
||||
FROM dbo.WritingPlans
|
||||
WHERE WritingPlanID = @WritingPlanID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.CreateWritingPlan
|
||||
@UserID int,
|
||||
@ProjectID int,
|
||||
@BookID int = NULL,
|
||||
@PlanName nvarchar(200),
|
||||
@GoalType nvarchar(50),
|
||||
@StartDate date,
|
||||
@DeadlineDate date,
|
||||
@AvailableDaysJson nvarchar(max),
|
||||
@SessionLengthMinutes int,
|
||||
@IncludeBlockedScenes bit = 0,
|
||||
@RebalanceMode nvarchar(50) = N'Ask',
|
||||
@IsActive bit = 1
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
INSERT dbo.WritingPlans
|
||||
(
|
||||
UserID, ProjectID, BookID, PlanName, GoalType, StartDate, DeadlineDate, AvailableDaysJson,
|
||||
SessionLengthMinutes, IncludeBlockedScenes, RebalanceMode, IsActive
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@UserID, @ProjectID, @BookID, @PlanName, @GoalType, @StartDate, @DeadlineDate, @AvailableDaysJson,
|
||||
@SessionLengthMinutes, @IncludeBlockedScenes, @RebalanceMode, @IsActive
|
||||
);
|
||||
|
||||
SELECT CONVERT(int, SCOPE_IDENTITY());
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.UpdateWritingPlan
|
||||
@WritingPlanID int,
|
||||
@UserID int,
|
||||
@ProjectID int,
|
||||
@BookID int = NULL,
|
||||
@PlanName nvarchar(200),
|
||||
@GoalType nvarchar(50),
|
||||
@StartDate date,
|
||||
@DeadlineDate date,
|
||||
@AvailableDaysJson nvarchar(max),
|
||||
@SessionLengthMinutes int,
|
||||
@IncludeBlockedScenes bit = 0,
|
||||
@RebalanceMode nvarchar(50) = N'Ask',
|
||||
@IsActive bit = 1
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
UPDATE dbo.WritingPlans
|
||||
SET UserID = @UserID,
|
||||
ProjectID = @ProjectID,
|
||||
BookID = @BookID,
|
||||
PlanName = @PlanName,
|
||||
GoalType = @GoalType,
|
||||
StartDate = @StartDate,
|
||||
DeadlineDate = @DeadlineDate,
|
||||
AvailableDaysJson = @AvailableDaysJson,
|
||||
SessionLengthMinutes = @SessionLengthMinutes,
|
||||
IncludeBlockedScenes = @IncludeBlockedScenes,
|
||||
RebalanceMode = @RebalanceMode,
|
||||
IsActive = @IsActive,
|
||||
ModifiedDateUtc = GETUTCDATE()
|
||||
WHERE WritingPlanID = @WritingPlanID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.DeleteWritingPlan
|
||||
@WritingPlanID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DELETE FROM dbo.WritingScheduleItems
|
||||
WHERE WritingPlanID = @WritingPlanID;
|
||||
|
||||
DELETE FROM dbo.WritingPlans
|
||||
WHERE WritingPlanID = @WritingPlanID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.SetWritingPlanActive
|
||||
@WritingPlanID int,
|
||||
@IsActive bit
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
UPDATE dbo.WritingPlans
|
||||
SET IsActive = @IsActive,
|
||||
ModifiedDateUtc = GETUTCDATE()
|
||||
WHERE WritingPlanID = @WritingPlanID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.GetScheduleItemsByPlan
|
||||
@WritingPlanID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT WritingScheduleItemID, WritingPlanID, SceneID, ScheduledDate, TaskType, EstimatedMinutes,
|
||||
TargetWords, ScheduleStatus, CompletedDateUtc, Notes, CreatedDateUtc, ModifiedDateUtc
|
||||
FROM dbo.WritingScheduleItems
|
||||
WHERE WritingPlanID = @WritingPlanID
|
||||
ORDER BY ScheduledDate, WritingScheduleItemID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.GetScheduleItemsByDateRange
|
||||
@WritingPlanID int,
|
||||
@StartDate date,
|
||||
@EndDate date
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT WritingScheduleItemID, WritingPlanID, SceneID, ScheduledDate, TaskType, EstimatedMinutes,
|
||||
TargetWords, ScheduleStatus, CompletedDateUtc, Notes, CreatedDateUtc, ModifiedDateUtc
|
||||
FROM dbo.WritingScheduleItems
|
||||
WHERE WritingPlanID = @WritingPlanID
|
||||
AND ScheduledDate >= @StartDate
|
||||
AND ScheduledDate <= @EndDate
|
||||
ORDER BY ScheduledDate, WritingScheduleItemID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.CreateScheduleItem
|
||||
@WritingPlanID int,
|
||||
@SceneID int,
|
||||
@ScheduledDate date,
|
||||
@TaskType nvarchar(50),
|
||||
@EstimatedMinutes int = NULL,
|
||||
@TargetWords int = NULL,
|
||||
@ScheduleStatus nvarchar(50),
|
||||
@CompletedDateUtc datetime2 = NULL,
|
||||
@Notes nvarchar(1000) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
INSERT dbo.WritingScheduleItems
|
||||
(
|
||||
WritingPlanID, SceneID, ScheduledDate, TaskType, EstimatedMinutes, TargetWords,
|
||||
ScheduleStatus, CompletedDateUtc, Notes
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@WritingPlanID, @SceneID, @ScheduledDate, @TaskType, @EstimatedMinutes, @TargetWords,
|
||||
@ScheduleStatus, @CompletedDateUtc, @Notes
|
||||
);
|
||||
|
||||
SELECT CONVERT(int, SCOPE_IDENTITY());
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.UpdateScheduleItem
|
||||
@WritingScheduleItemID int,
|
||||
@WritingPlanID int,
|
||||
@SceneID int,
|
||||
@ScheduledDate date,
|
||||
@TaskType nvarchar(50),
|
||||
@EstimatedMinutes int = NULL,
|
||||
@TargetWords int = NULL,
|
||||
@ScheduleStatus nvarchar(50),
|
||||
@CompletedDateUtc datetime2 = NULL,
|
||||
@Notes nvarchar(1000) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
UPDATE dbo.WritingScheduleItems
|
||||
SET WritingPlanID = @WritingPlanID,
|
||||
SceneID = @SceneID,
|
||||
ScheduledDate = @ScheduledDate,
|
||||
TaskType = @TaskType,
|
||||
EstimatedMinutes = @EstimatedMinutes,
|
||||
TargetWords = @TargetWords,
|
||||
ScheduleStatus = @ScheduleStatus,
|
||||
CompletedDateUtc = @CompletedDateUtc,
|
||||
Notes = @Notes,
|
||||
ModifiedDateUtc = GETUTCDATE()
|
||||
WHERE WritingScheduleItemID = @WritingScheduleItemID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.DeleteScheduleItem
|
||||
@WritingScheduleItemID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DELETE FROM dbo.WritingScheduleItems
|
||||
WHERE WritingScheduleItemID = @WritingScheduleItemID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.UpdateScheduleItemStatus
|
||||
@WritingScheduleItemID int,
|
||||
@ScheduleStatus nvarchar(50),
|
||||
@CompletedDateUtc datetime2 = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
UPDATE dbo.WritingScheduleItems
|
||||
SET ScheduleStatus = @ScheduleStatus,
|
||||
CompletedDateUtc = @CompletedDateUtc,
|
||||
ModifiedDateUtc = GETUTCDATE()
|
||||
WHERE WritingScheduleItemID = @WritingScheduleItemID;
|
||||
END;
|
||||
GO
|
||||
@ -321,6 +321,41 @@ public sealed class WriterDashboardViewModel
|
||||
public IReadOnlyList<StoryBibleAttentionItem> StoryHealthReminders { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class WritingPlanViewModel
|
||||
{
|
||||
public int WritingPlanID { get; set; }
|
||||
public int UserID { get; set; }
|
||||
public int ProjectID { get; set; }
|
||||
public int? BookID { get; set; }
|
||||
public string PlanName { get; set; } = string.Empty;
|
||||
public string GoalType { get; set; } = string.Empty;
|
||||
public DateTime StartDate { get; set; }
|
||||
public DateTime DeadlineDate { get; set; }
|
||||
public string AvailableDaysJson { get; set; } = string.Empty;
|
||||
public int SessionLengthMinutes { get; set; }
|
||||
public bool IncludeBlockedScenes { get; set; }
|
||||
public string RebalanceMode { get; set; } = "Ask";
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime CreatedDateUtc { get; set; }
|
||||
public DateTime ModifiedDateUtc { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WritingScheduleItemViewModel
|
||||
{
|
||||
public int WritingScheduleItemID { get; set; }
|
||||
public int WritingPlanID { get; set; }
|
||||
public int SceneID { get; set; }
|
||||
public DateTime ScheduledDate { get; set; }
|
||||
public string TaskType { get; set; } = string.Empty;
|
||||
public int? EstimatedMinutes { get; set; }
|
||||
public int? TargetWords { get; set; }
|
||||
public string ScheduleStatus { get; set; } = string.Empty;
|
||||
public DateTime? CompletedDateUtc { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public DateTime CreatedDateUtc { get; set; }
|
||||
public DateTime ModifiedDateUtc { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CharacterArcViewModel
|
||||
{
|
||||
public Project Project { get; set; } = new();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user