Replace status_id with in_progress boolean field
- Remove task_statuses lookup table and StatusID foreign key - Add InProgress boolean field to Task model - Add database migration (005_replace_status_with_in_progress) - Update all handlers, services, and repositories - Update admin frontend to display in_progress as checkbox/boolean - Remove Task Statuses tab from admin lookups page - Update tests to use InProgress instead of StatusID - Task categorization now uses InProgress for kanban column assignment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -168,7 +168,6 @@ const (
|
||||
LookupKeyPrefix = "lookup:"
|
||||
LookupCategoriesKey = LookupKeyPrefix + "categories"
|
||||
LookupPrioritiesKey = LookupKeyPrefix + "priorities"
|
||||
LookupStatusesKey = LookupKeyPrefix + "statuses"
|
||||
LookupFrequenciesKey = LookupKeyPrefix + "frequencies"
|
||||
LookupResidenceTypesKey = LookupKeyPrefix + "residence_types"
|
||||
LookupSpecialtiesKey = LookupKeyPrefix + "specialties"
|
||||
@@ -196,7 +195,6 @@ func (c *CacheService) InvalidateAllLookups(ctx context.Context) error {
|
||||
keys := []string{
|
||||
LookupCategoriesKey,
|
||||
LookupPrioritiesKey,
|
||||
LookupStatusesKey,
|
||||
LookupFrequenciesKey,
|
||||
LookupResidenceTypesKey,
|
||||
LookupSpecialtiesKey,
|
||||
@@ -239,21 +237,6 @@ func (c *CacheService) InvalidatePriorities(ctx context.Context) error {
|
||||
return c.Delete(ctx, LookupPrioritiesKey, StaticDataKey)
|
||||
}
|
||||
|
||||
// CacheStatuses caches task statuses
|
||||
func (c *CacheService) CacheStatuses(ctx context.Context, data interface{}) error {
|
||||
return c.CacheLookupData(ctx, LookupStatusesKey, data)
|
||||
}
|
||||
|
||||
// GetCachedStatuses retrieves cached task statuses
|
||||
func (c *CacheService) GetCachedStatuses(ctx context.Context, dest interface{}) error {
|
||||
return c.GetCachedLookupData(ctx, LookupStatusesKey, dest)
|
||||
}
|
||||
|
||||
// InvalidateStatuses removes cached task statuses
|
||||
func (c *CacheService) InvalidateStatuses(ctx context.Context) error {
|
||||
return c.Delete(ctx, LookupStatusesKey, StaticDataKey)
|
||||
}
|
||||
|
||||
// CacheFrequencies caches task frequencies
|
||||
func (c *CacheService) CacheFrequencies(ctx context.Context, data interface{}) error {
|
||||
return c.CacheLookupData(ctx, LookupFrequenciesKey, data)
|
||||
|
||||
@@ -616,8 +616,8 @@ func (s *ResidenceService) GenerateTasksReport(residenceID, userID uint) (*Tasks
|
||||
if task.Priority != nil {
|
||||
taskData.Priority = task.Priority.Name
|
||||
}
|
||||
if task.Status != nil {
|
||||
taskData.Status = task.Status.Name
|
||||
if task.InProgress {
|
||||
taskData.Status = "In Progress"
|
||||
}
|
||||
// Use effective date for report (NextDueDate ?? DueDate)
|
||||
effectiveDate := predicates.EffectiveDate(&task)
|
||||
|
||||
@@ -42,12 +42,12 @@ func TestResidenceService_CreateResidence(t *testing.T) {
|
||||
resp, err := service.CreateResidence(req, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, "Test House", resp.Name)
|
||||
assert.Equal(t, "123 Main St", resp.StreetAddress)
|
||||
assert.Equal(t, "Austin", resp.City)
|
||||
assert.Equal(t, "TX", resp.StateProvince)
|
||||
assert.Equal(t, "USA", resp.Country) // Default country
|
||||
assert.True(t, resp.IsPrimary) // Default is_primary
|
||||
assert.Equal(t, "Test House", resp.Data.Name)
|
||||
assert.Equal(t, "123 Main St", resp.Data.StreetAddress)
|
||||
assert.Equal(t, "Austin", resp.Data.City)
|
||||
assert.Equal(t, "TX", resp.Data.StateProvince)
|
||||
assert.Equal(t, "USA", resp.Data.Country) // Default country
|
||||
assert.True(t, resp.Data.IsPrimary) // Default is_primary
|
||||
}
|
||||
|
||||
func TestResidenceService_CreateResidence_WithOptionalFields(t *testing.T) {
|
||||
@@ -79,12 +79,12 @@ func TestResidenceService_CreateResidence_WithOptionalFields(t *testing.T) {
|
||||
|
||||
resp, err := service.CreateResidence(req, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Canada", resp.Country)
|
||||
assert.Equal(t, 3, *resp.Bedrooms)
|
||||
assert.True(t, resp.Bathrooms.Equal(decimal.NewFromFloat(2.5)))
|
||||
assert.Equal(t, 2000, *resp.SquareFootage)
|
||||
assert.Equal(t, "Canada", resp.Data.Country)
|
||||
assert.Equal(t, 3, *resp.Data.Bedrooms)
|
||||
assert.True(t, resp.Data.Bathrooms.Equal(decimal.NewFromFloat(2.5)))
|
||||
assert.Equal(t, 2000, *resp.Data.SquareFootage)
|
||||
// First residence defaults to primary regardless of request
|
||||
assert.True(t, resp.IsPrimary)
|
||||
assert.True(t, resp.Data.IsPrimary)
|
||||
}
|
||||
|
||||
func TestResidenceService_GetResidence(t *testing.T) {
|
||||
@@ -166,8 +166,8 @@ func TestResidenceService_UpdateResidence(t *testing.T) {
|
||||
|
||||
resp, err := service.UpdateResidence(residence.ID, user.ID, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated Name", resp.Name)
|
||||
assert.Equal(t, "Dallas", resp.City)
|
||||
assert.Equal(t, "Updated Name", resp.Data.Name)
|
||||
assert.Equal(t, "Dallas", resp.Data.City)
|
||||
}
|
||||
|
||||
func TestResidenceService_UpdateResidence_NotOwner(t *testing.T) {
|
||||
@@ -201,7 +201,7 @@ func TestResidenceService_DeleteResidence(t *testing.T) {
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
err := service.DeleteResidence(residence.ID, user.ID)
|
||||
_, err := service.DeleteResidence(residence.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should not be found
|
||||
@@ -221,7 +221,7 @@ func TestResidenceService_DeleteResidence_NotOwner(t *testing.T) {
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
residenceRepo.AddUser(residence.ID, sharedUser.ID)
|
||||
|
||||
err := service.DeleteResidence(residence.ID, sharedUser.ID)
|
||||
_, err := service.DeleteResidence(residence.ID, sharedUser.ID)
|
||||
assert.ErrorIs(t, err, ErrNotResidenceOwner)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ KANBAN COLUMNS (in priority order):
|
||||
----------------------------------
|
||||
1. CANCELLED: Task.IsCancelled = true
|
||||
2. COMPLETED: NextDueDate = nil AND has completions (one-time task done)
|
||||
3. IN_PROGRESS: Status.Name = "In Progress"
|
||||
3. IN_PROGRESS: InProgress = true
|
||||
4. OVERDUE: NextDueDate < now
|
||||
5. DUE_SOON: NextDueDate < now + daysThreshold (default 30)
|
||||
6. UPCOMING: Everything else (NextDueDate >= threshold or no due date)
|
||||
@@ -72,6 +72,14 @@ func daysAgo(n int) time.Time {
|
||||
return time.Now().UTC().AddDate(0, 0, -n)
|
||||
}
|
||||
|
||||
// isTaskCompleted checks if a task is permanently completed (one-time task done).
|
||||
// A task is completed when it has completions AND NextDueDate is nil.
|
||||
func isTaskCompleted(task *models.Task) bool {
|
||||
if len(task.Completions) == 0 {
|
||||
return false
|
||||
}
|
||||
return task.NextDueDate == nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// isTaskCompleted FUNCTION TESTS
|
||||
@@ -157,7 +165,7 @@ func TestGetButtonTypesForTask_CompletedOneTimeTask(t *testing.T) {
|
||||
func TestGetButtonTypesForTask_InProgressTask(t *testing.T) {
|
||||
task := &models.Task{
|
||||
NextDueDate: ptr(daysFromNow(10)),
|
||||
Status: &models.TaskStatus{Name: "In Progress"},
|
||||
InProgress: true,
|
||||
}
|
||||
|
||||
buttons := GetButtonTypesForTask(task, 30)
|
||||
@@ -237,7 +245,7 @@ func TestGetIOSCategoryForTask_CompletedTask(t *testing.T) {
|
||||
func TestGetIOSCategoryForTask_InProgressTask(t *testing.T) {
|
||||
task := &models.Task{
|
||||
NextDueDate: ptr(daysFromNow(10)),
|
||||
Status: &models.TaskStatus{Name: "In Progress"},
|
||||
InProgress: true,
|
||||
}
|
||||
|
||||
category := GetIOSCategoryForTask(task)
|
||||
@@ -285,7 +293,7 @@ func TestDetermineKanbanColumn_CompletedOneTimeTask(t *testing.T) {
|
||||
func TestDetermineKanbanColumn_InProgressTask(t *testing.T) {
|
||||
task := &models.Task{
|
||||
NextDueDate: ptr(daysAgo(5)), // Even overdue
|
||||
Status: &models.TaskStatus{Name: "In Progress"},
|
||||
InProgress: true,
|
||||
}
|
||||
|
||||
column := responses.DetermineKanbanColumn(task, 30)
|
||||
@@ -902,7 +910,7 @@ func TestEdgeCase_CancelledAndOverdue(t *testing.T) {
|
||||
func TestEdgeCase_InProgressAndOverdue(t *testing.T) {
|
||||
task := &models.Task{
|
||||
NextDueDate: ptr(daysAgo(5)),
|
||||
Status: &models.TaskStatus{Name: "In Progress"},
|
||||
InProgress: true,
|
||||
}
|
||||
|
||||
column := responses.DetermineKanbanColumn(task, 30)
|
||||
@@ -1011,7 +1019,7 @@ func TestButtonTypes_ConsistencyWithKanbanColumn(t *testing.T) {
|
||||
name: "In Progress task",
|
||||
task: &models.Task{
|
||||
NextDueDate: ptr(daysFromNow(10)),
|
||||
Status: &models.TaskStatus{Name: "In Progress"},
|
||||
InProgress: true,
|
||||
},
|
||||
expectedColumn: "in_progress_tasks",
|
||||
expectedButtons: []string{"edit", "complete", "cancel"},
|
||||
@@ -1062,7 +1070,7 @@ func TestPriorityOrder_CancelledBeatsEverything(t *testing.T) {
|
||||
task := &models.Task{
|
||||
IsCancelled: true,
|
||||
NextDueDate: ptr(daysAgo(10)),
|
||||
Status: &models.TaskStatus{Name: "In Progress"},
|
||||
InProgress: true,
|
||||
Completions: []models.TaskCompletion{{CompletedAt: daysAgo(1)}},
|
||||
}
|
||||
|
||||
@@ -1074,7 +1082,7 @@ func TestPriorityOrder_CompletedBeatsInProgress(t *testing.T) {
|
||||
// One-time task with In Progress status but completed
|
||||
task := &models.Task{
|
||||
NextDueDate: nil,
|
||||
Status: &models.TaskStatus{Name: "In Progress"},
|
||||
InProgress: true,
|
||||
Completions: []models.TaskCompletion{{CompletedAt: daysAgo(1)}},
|
||||
}
|
||||
|
||||
@@ -1086,7 +1094,7 @@ func TestPriorityOrder_InProgressBeatsDateBased(t *testing.T) {
|
||||
// Overdue task that's in progress
|
||||
task := &models.Task{
|
||||
NextDueDate: ptr(daysAgo(10)),
|
||||
Status: &models.TaskStatus{Name: "In Progress"},
|
||||
InProgress: true,
|
||||
}
|
||||
|
||||
column := responses.DetermineKanbanColumn(task, 30)
|
||||
|
||||
@@ -173,8 +173,8 @@ func (s *TaskService) CreateTask(req *requests.CreateTaskRequest, userID uint) (
|
||||
Description: req.Description,
|
||||
CategoryID: req.CategoryID,
|
||||
PriorityID: req.PriorityID,
|
||||
StatusID: req.StatusID,
|
||||
FrequencyID: req.FrequencyID,
|
||||
InProgress: req.InProgress,
|
||||
AssignedToID: req.AssignedToID,
|
||||
DueDate: dueDate,
|
||||
NextDueDate: dueDate, // Initialize next_due_date to due_date
|
||||
@@ -230,12 +230,12 @@ func (s *TaskService) UpdateTask(taskID, userID uint, req *requests.UpdateTaskRe
|
||||
if req.PriorityID != nil {
|
||||
task.PriorityID = req.PriorityID
|
||||
}
|
||||
if req.StatusID != nil {
|
||||
task.StatusID = req.StatusID
|
||||
}
|
||||
if req.FrequencyID != nil {
|
||||
task.FrequencyID = req.FrequencyID
|
||||
}
|
||||
if req.InProgress != nil {
|
||||
task.InProgress = *req.InProgress
|
||||
}
|
||||
if req.AssignedToID != nil {
|
||||
task.AssignedToID = req.AssignedToID
|
||||
}
|
||||
@@ -324,13 +324,7 @@ func (s *TaskService) MarkInProgress(taskID, userID uint) (*responses.TaskWithSu
|
||||
return nil, ErrTaskAccessDenied
|
||||
}
|
||||
|
||||
// Find "In Progress" status
|
||||
status, err := s.taskRepo.FindStatusByName("In Progress")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.taskRepo.MarkInProgress(taskID, status.ID); err != nil {
|
||||
if err := s.taskRepo.MarkInProgress(taskID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -534,24 +528,22 @@ func (s *TaskService) CreateCompletion(req *requests.CreateTaskCompletionRequest
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update next_due_date and status based on frequency
|
||||
// - If frequency is "Once" (days = nil or 0), set next_due_date to nil and status to "Completed"
|
||||
// Update next_due_date and in_progress based on frequency
|
||||
// - If frequency is "Once" (days = nil or 0), set next_due_date to nil (marks as completed)
|
||||
// - If frequency is recurring, calculate next_due_date = completion_date + frequency_days
|
||||
// and reset status to "Pending" so task shows in correct kanban column
|
||||
// and reset in_progress to false so task shows in correct kanban column
|
||||
if task.Frequency == nil || task.Frequency.Days == nil || *task.Frequency.Days == 0 {
|
||||
// One-time task - clear next_due_date and set status to "Completed" (ID=3)
|
||||
// One-time task - clear next_due_date (completion is determined by NextDueDate == nil + has completions)
|
||||
task.NextDueDate = nil
|
||||
completedStatusID := uint(3)
|
||||
task.StatusID = &completedStatusID
|
||||
task.InProgress = false
|
||||
} else {
|
||||
// Recurring task - calculate next due date from completion date + frequency
|
||||
nextDue := completedAt.AddDate(0, 0, *task.Frequency.Days)
|
||||
task.NextDueDate = &nextDue
|
||||
|
||||
// Reset status to "Pending" (ID=1) so task appears in upcoming/due_soon
|
||||
// Reset in_progress to false so task appears in upcoming/due_soon
|
||||
// instead of staying in "In Progress" column
|
||||
pendingStatusID := uint(1)
|
||||
task.StatusID = &pendingStatusID
|
||||
task.InProgress = false
|
||||
}
|
||||
if err := s.taskRepo.Update(task); err != nil {
|
||||
log.Error().Err(err).Uint("task_id", task.ID).Msg("Failed to update task after completion")
|
||||
@@ -633,20 +625,18 @@ func (s *TaskService) QuickComplete(taskID uint, userID uint) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update next_due_date and status based on frequency
|
||||
// Update next_due_date and in_progress based on frequency
|
||||
if task.Frequency == nil || task.Frequency.Days == nil || *task.Frequency.Days == 0 {
|
||||
// One-time task - clear next_due_date and set status to "Completed" (ID=3)
|
||||
// One-time task - clear next_due_date (completion is determined by NextDueDate == nil + has completions)
|
||||
task.NextDueDate = nil
|
||||
completedStatusID := uint(3)
|
||||
task.StatusID = &completedStatusID
|
||||
task.InProgress = false
|
||||
} else {
|
||||
// Recurring task - calculate next due date from completion date + frequency
|
||||
nextDue := completedAt.AddDate(0, 0, *task.Frequency.Days)
|
||||
task.NextDueDate = &nextDue
|
||||
|
||||
// Reset status to "Pending" (ID=1)
|
||||
pendingStatusID := uint(1)
|
||||
task.StatusID = &pendingStatusID
|
||||
// Reset in_progress to false
|
||||
task.InProgress = false
|
||||
}
|
||||
if err := s.taskRepo.Update(task); err != nil {
|
||||
log.Error().Err(err).Uint("task_id", task.ID).Msg("Failed to update task after quick completion")
|
||||
@@ -858,20 +848,6 @@ func (s *TaskService) GetPriorities() ([]responses.TaskPriorityResponse, error)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetStatuses returns all task statuses
|
||||
func (s *TaskService) GetStatuses() ([]responses.TaskStatusResponse, error) {
|
||||
statuses, err := s.taskRepo.GetAllStatuses()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]responses.TaskStatusResponse, len(statuses))
|
||||
for i, st := range statuses {
|
||||
result[i] = *responses.NewTaskStatusResponse(&st)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetFrequencies returns all task frequencies
|
||||
func (s *TaskService) GetFrequencies() ([]responses.TaskFrequencyResponse, error) {
|
||||
frequencies, err := s.taskRepo.GetAllFrequencies()
|
||||
|
||||
@@ -41,9 +41,9 @@ func TestTaskService_CreateTask(t *testing.T) {
|
||||
|
||||
resp, err := service.CreateTask(req, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, resp.ID)
|
||||
assert.Equal(t, "Fix leaky faucet", resp.Title)
|
||||
assert.Equal(t, "Kitchen faucet is dripping", resp.Description)
|
||||
assert.NotZero(t, resp.Data.ID)
|
||||
assert.Equal(t, "Fix leaky faucet", resp.Data.Title)
|
||||
assert.Equal(t, "Kitchen faucet is dripping", resp.Data.Description)
|
||||
}
|
||||
|
||||
func TestTaskService_CreateTask_WithOptionalFields(t *testing.T) {
|
||||
@@ -76,10 +76,10 @@ func TestTaskService_CreateTask_WithOptionalFields(t *testing.T) {
|
||||
|
||||
resp, err := service.CreateTask(req, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp.Category)
|
||||
assert.NotNil(t, resp.Priority)
|
||||
assert.NotNil(t, resp.DueDate)
|
||||
assert.NotNil(t, resp.EstimatedCost)
|
||||
assert.NotNil(t, resp.Data.Category)
|
||||
assert.NotNil(t, resp.Data.Priority)
|
||||
assert.NotNil(t, resp.Data.DueDate)
|
||||
assert.NotNil(t, resp.Data.EstimatedCost)
|
||||
}
|
||||
|
||||
func TestTaskService_CreateTask_AccessDenied(t *testing.T) {
|
||||
@@ -180,8 +180,8 @@ func TestTaskService_UpdateTask(t *testing.T) {
|
||||
|
||||
resp, err := service.UpdateTask(task.ID, user.ID, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated Title", resp.Title)
|
||||
assert.Equal(t, "Updated description", resp.Description)
|
||||
assert.Equal(t, "Updated Title", resp.Data.Title)
|
||||
assert.Equal(t, "Updated description", resp.Data.Description)
|
||||
}
|
||||
|
||||
func TestTaskService_DeleteTask(t *testing.T) {
|
||||
@@ -195,7 +195,7 @@ func TestTaskService_DeleteTask(t *testing.T) {
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
err := service.DeleteTask(task.ID, user.ID)
|
||||
_, err := service.DeleteTask(task.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = service.GetTask(task.ID, user.ID)
|
||||
@@ -215,7 +215,7 @@ func TestTaskService_CancelTask(t *testing.T) {
|
||||
|
||||
resp, err := service.CancelTask(task.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.IsCancelled)
|
||||
assert.True(t, resp.Data.IsCancelled)
|
||||
}
|
||||
|
||||
func TestTaskService_CancelTask_AlreadyCancelled(t *testing.T) {
|
||||
@@ -248,7 +248,7 @@ func TestTaskService_UncancelTask(t *testing.T) {
|
||||
service.CancelTask(task.ID, user.ID)
|
||||
resp, err := service.UncancelTask(task.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, resp.IsCancelled)
|
||||
assert.False(t, resp.Data.IsCancelled)
|
||||
}
|
||||
|
||||
func TestTaskService_ArchiveTask(t *testing.T) {
|
||||
@@ -264,7 +264,7 @@ func TestTaskService_ArchiveTask(t *testing.T) {
|
||||
|
||||
resp, err := service.ArchiveTask(task.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.IsArchived)
|
||||
assert.True(t, resp.Data.IsArchived)
|
||||
}
|
||||
|
||||
func TestTaskService_UnarchiveTask(t *testing.T) {
|
||||
@@ -281,7 +281,7 @@ func TestTaskService_UnarchiveTask(t *testing.T) {
|
||||
service.ArchiveTask(task.ID, user.ID)
|
||||
resp, err := service.UnarchiveTask(task.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, resp.IsArchived)
|
||||
assert.False(t, resp.Data.IsArchived)
|
||||
}
|
||||
|
||||
func TestTaskService_MarkInProgress(t *testing.T) {
|
||||
@@ -297,8 +297,7 @@ func TestTaskService_MarkInProgress(t *testing.T) {
|
||||
|
||||
resp, err := service.MarkInProgress(task.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp.Status)
|
||||
assert.Equal(t, "In Progress", resp.Status.Name)
|
||||
assert.True(t, resp.Data.InProgress)
|
||||
}
|
||||
|
||||
func TestTaskService_CreateCompletion(t *testing.T) {
|
||||
@@ -319,12 +318,12 @@ func TestTaskService_CreateCompletion(t *testing.T) {
|
||||
|
||||
resp, err := service.CreateCompletion(req, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, resp.ID)
|
||||
assert.Equal(t, task.ID, resp.TaskID)
|
||||
assert.Equal(t, "Completed successfully", resp.Notes)
|
||||
assert.NotZero(t, resp.Data.ID)
|
||||
assert.Equal(t, task.ID, resp.Data.TaskID)
|
||||
assert.Equal(t, "Completed successfully", resp.Data.Notes)
|
||||
}
|
||||
|
||||
func TestTaskService_CreateCompletion_RecurringTask_ResetsStatusToPending(t *testing.T) {
|
||||
func TestTaskService_CreateCompletion_RecurringTask_ResetsInProgress(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
taskRepo := repositories.NewTaskRepository(db)
|
||||
@@ -334,20 +333,16 @@ func TestTaskService_CreateCompletion_RecurringTask_ResetsStatusToPending(t *tes
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
// Get the "In Progress" status (ID=2) and a recurring frequency
|
||||
var inProgressStatus models.TaskStatus
|
||||
db.Where("name = ?", "In Progress").First(&inProgressStatus)
|
||||
|
||||
var monthlyFrequency models.TaskFrequency
|
||||
db.Where("name = ?", "Monthly").First(&monthlyFrequency)
|
||||
|
||||
// Create a recurring task with "In Progress" status
|
||||
// Create a recurring task that is in progress
|
||||
dueDate := time.Now().AddDate(0, 0, 7) // Due in 7 days
|
||||
task := &models.Task{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Recurring Task",
|
||||
StatusID: &inProgressStatus.ID,
|
||||
InProgress: true,
|
||||
FrequencyID: &monthlyFrequency.ID,
|
||||
DueDate: &dueDate,
|
||||
NextDueDate: &dueDate,
|
||||
@@ -365,24 +360,21 @@ func TestTaskService_CreateCompletion_RecurringTask_ResetsStatusToPending(t *tes
|
||||
|
||||
resp, err := service.CreateCompletion(req, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, resp.ID)
|
||||
assert.NotZero(t, resp.Data.ID)
|
||||
|
||||
// Verify the task in the response has status reset to "Pending" (ID=1)
|
||||
require.NotNil(t, resp.Task, "Response should include the updated task")
|
||||
require.NotNil(t, resp.Task.StatusID, "Task should have a status ID")
|
||||
assert.Equal(t, uint(1), *resp.Task.StatusID, "Recurring task status should be reset to Pending (ID=1) after completion")
|
||||
// Verify the task in the response has InProgress reset to false
|
||||
require.NotNil(t, resp.Data.Task, "Response should include the updated task")
|
||||
assert.False(t, resp.Data.Task.InProgress, "Recurring task InProgress should be reset to false after completion")
|
||||
|
||||
// Verify NextDueDate was updated (should be ~30 days from now for monthly)
|
||||
require.NotNil(t, resp.Task.NextDueDate, "Recurring task should have NextDueDate set")
|
||||
require.NotNil(t, resp.Data.Task.NextDueDate, "Recurring task should have NextDueDate set")
|
||||
expectedNextDue := time.Now().AddDate(0, 0, 30) // Monthly = 30 days
|
||||
assert.WithinDuration(t, expectedNextDue, *resp.Task.NextDueDate, 24*time.Hour, "NextDueDate should be approximately 30 days from now")
|
||||
assert.WithinDuration(t, expectedNextDue, *resp.Data.Task.NextDueDate, 24*time.Hour, "NextDueDate should be approximately 30 days from now")
|
||||
|
||||
// Also verify by reloading from database directly
|
||||
var reloadedTask models.Task
|
||||
db.Preload("Status").First(&reloadedTask, task.ID)
|
||||
require.NotNil(t, reloadedTask.StatusID)
|
||||
assert.Equal(t, uint(1), *reloadedTask.StatusID, "Database should show Pending status")
|
||||
assert.Equal(t, "Pending", reloadedTask.Status.Name)
|
||||
db.First(&reloadedTask, task.ID)
|
||||
assert.False(t, reloadedTask.InProgress, "Database should show InProgress=false")
|
||||
}
|
||||
|
||||
func TestTaskService_GetCompletion(t *testing.T) {
|
||||
@@ -428,7 +420,7 @@ func TestTaskService_DeleteCompletion(t *testing.T) {
|
||||
}
|
||||
db.Create(completion)
|
||||
|
||||
err := service.DeleteCompletion(completion.ID, user.ID)
|
||||
_, err := service.DeleteCompletion(completion.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = service.GetCompletion(completion.ID, user.ID)
|
||||
@@ -470,18 +462,6 @@ func TestTaskService_GetPriorities(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskService_GetStatuses(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
taskRepo := repositories.NewTaskRepository(db)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
service := NewTaskService(taskRepo, residenceRepo)
|
||||
|
||||
statuses, err := service.GetStatuses()
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, len(statuses), 0)
|
||||
}
|
||||
|
||||
func TestTaskService_GetFrequencies(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
|
||||
Reference in New Issue
Block a user