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);
|
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
|
public interface IExportRepository
|
||||||
{
|
{
|
||||||
Task<ExportPacket> GetStoryBibleAsync(int projectId);
|
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 sealed class ExportRepository(ISqlConnectionFactory connectionFactory) : IExportRepository
|
||||||
{
|
{
|
||||||
public async Task<ExportPacket> GetStoryBibleAsync(int projectId)
|
public async Task<ExportPacket> GetStoryBibleAsync(int projectId)
|
||||||
|
|||||||
@ -1572,6 +1572,41 @@ public sealed class WriterDashboardScene
|
|||||||
public string? PinnedNotes { get; set; }
|
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 sealed class ChapterWorkflowScene
|
||||||
{
|
{
|
||||||
public int SceneID { get; set; }
|
public int SceneID { get; set; }
|
||||||
|
|||||||
@ -96,6 +96,7 @@ public class Program
|
|||||||
builder.Services.AddScoped<IAnalyticsRepository, AnalyticsRepository>();
|
builder.Services.AddScoped<IAnalyticsRepository, AnalyticsRepository>();
|
||||||
builder.Services.AddScoped<IStoryBibleRepository, StoryBibleRepository>();
|
builder.Services.AddScoped<IStoryBibleRepository, StoryBibleRepository>();
|
||||||
builder.Services.AddScoped<IWriterWorkspaceRepository, WriterWorkspaceRepository>();
|
builder.Services.AddScoped<IWriterWorkspaceRepository, WriterWorkspaceRepository>();
|
||||||
|
builder.Services.AddScoped<IWritingScheduleRepository, WritingScheduleRepository>();
|
||||||
builder.Services.AddScoped<IExportRepository, ExportRepository>();
|
builder.Services.AddScoped<IExportRepository, ExportRepository>();
|
||||||
builder.Services.AddScoped<IProjectBackupRepository, ProjectBackupRepository>();
|
builder.Services.AddScoped<IProjectBackupRepository, ProjectBackupRepository>();
|
||||||
builder.Services.AddScoped<IProjectRestorePointRepository, ProjectRestorePointRepository>();
|
builder.Services.AddScoped<IProjectRestorePointRepository, ProjectRestorePointRepository>();
|
||||||
@ -127,6 +128,7 @@ public class Program
|
|||||||
builder.Services.AddScoped<IStoryBibleService, StoryBibleService>();
|
builder.Services.AddScoped<IStoryBibleService, StoryBibleService>();
|
||||||
builder.Services.AddScoped<IRelationshipMapService, RelationshipMapService>();
|
builder.Services.AddScoped<IRelationshipMapService, RelationshipMapService>();
|
||||||
builder.Services.AddScoped<IWriterWorkspaceService, WriterWorkspaceService>();
|
builder.Services.AddScoped<IWriterWorkspaceService, WriterWorkspaceService>();
|
||||||
|
builder.Services.AddScoped<IWritingScheduleService, WritingScheduleService>();
|
||||||
builder.Services.AddScoped<IStorageService, StorageService>();
|
builder.Services.AddScoped<IStorageService, StorageService>();
|
||||||
builder.Services.AddScoped<IExportService, ExportService>();
|
builder.Services.AddScoped<IExportService, ExportService>();
|
||||||
builder.Services.AddScoped<IProjectExportService, ProjectExportService>();
|
builder.Services.AddScoped<IProjectExportService, ProjectExportService>();
|
||||||
|
|||||||
@ -256,6 +256,22 @@ public interface IWriterWorkspaceService
|
|||||||
Task DeleteAttachmentAsync(int sceneAttachmentId);
|
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
|
public interface IStorageService
|
||||||
{
|
{
|
||||||
Task<StorageUsage?> GetSceneStorageUsageAsync(int sceneId);
|
Task<StorageUsage?> GetSceneStorageUsageAsync(int sceneId);
|
||||||
@ -3259,6 +3275,165 @@ public sealed class WriterWorkspaceService(
|
|||||||
public Task DeleteAttachmentAsync(int sceneAttachmentId) => writerWorkspace.DeleteAttachmentAsync(sceneAttachmentId);
|
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 sealed class ExportService(IExportRepository exports) : IExportService
|
||||||
{
|
{
|
||||||
public async Task<(string FileName, string ContentType, string Content)> ExportStoryBibleAsync(int projectId, string format)
|
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 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 sealed class CharacterArcViewModel
|
||||||
{
|
{
|
||||||
public Project Project { get; set; } = new();
|
public Project Project { get; set; } = new();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user