Add per-residence overdue_count to API response
Adds overdueCount field to each residence in my-residences endpoint, enabling the mobile app to show pulsing icons on individual residence cards that have overdue tasks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -25,31 +25,32 @@ type ResidenceUserResponse struct {
|
|||||||
|
|
||||||
// ResidenceResponse represents a residence in the API response
|
// ResidenceResponse represents a residence in the API response
|
||||||
type ResidenceResponse struct {
|
type ResidenceResponse struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
OwnerID uint `json:"owner_id"`
|
OwnerID uint `json:"owner_id"`
|
||||||
Owner *ResidenceUserResponse `json:"owner,omitempty"`
|
Owner *ResidenceUserResponse `json:"owner,omitempty"`
|
||||||
Users []ResidenceUserResponse `json:"users,omitempty"`
|
Users []ResidenceUserResponse `json:"users,omitempty"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
PropertyTypeID *uint `json:"property_type_id"`
|
PropertyTypeID *uint `json:"property_type_id"`
|
||||||
PropertyType *ResidenceTypeResponse `json:"property_type,omitempty"`
|
PropertyType *ResidenceTypeResponse `json:"property_type,omitempty"`
|
||||||
StreetAddress string `json:"street_address"`
|
StreetAddress string `json:"street_address"`
|
||||||
ApartmentUnit string `json:"apartment_unit"`
|
ApartmentUnit string `json:"apartment_unit"`
|
||||||
City string `json:"city"`
|
City string `json:"city"`
|
||||||
StateProvince string `json:"state_province"`
|
StateProvince string `json:"state_province"`
|
||||||
PostalCode string `json:"postal_code"`
|
PostalCode string `json:"postal_code"`
|
||||||
Country string `json:"country"`
|
Country string `json:"country"`
|
||||||
Bedrooms *int `json:"bedrooms"`
|
Bedrooms *int `json:"bedrooms"`
|
||||||
Bathrooms *decimal.Decimal `json:"bathrooms"`
|
Bathrooms *decimal.Decimal `json:"bathrooms"`
|
||||||
SquareFootage *int `json:"square_footage"`
|
SquareFootage *int `json:"square_footage"`
|
||||||
LotSize *decimal.Decimal `json:"lot_size"`
|
LotSize *decimal.Decimal `json:"lot_size"`
|
||||||
YearBuilt *int `json:"year_built"`
|
YearBuilt *int `json:"year_built"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
PurchaseDate *time.Time `json:"purchase_date"`
|
PurchaseDate *time.Time `json:"purchase_date"`
|
||||||
PurchasePrice *decimal.Decimal `json:"purchase_price"`
|
PurchasePrice *decimal.Decimal `json:"purchase_price"`
|
||||||
IsPrimary bool `json:"is_primary"`
|
IsPrimary bool `json:"is_primary"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
OverdueCount int `json:"overdue_count"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TotalSummary represents summary statistics for all residences
|
// TotalSummary represents summary statistics for all residences
|
||||||
|
|||||||
@@ -502,3 +502,37 @@ func (r *TaskRepository) GetTaskStatistics(residenceIDs []uint) (*TaskStatistics
|
|||||||
TasksDueNextMonth: int(tasksDueNextMonth),
|
TasksDueNextMonth: int(tasksDueNextMonth),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOverdueCountByResidence returns a map of residence ID to overdue task count.
|
||||||
|
// Uses the task.scopes package for consistent filtering logic.
|
||||||
|
func (r *TaskRepository) GetOverdueCountByResidence(residenceIDs []uint) (map[uint]int, error) {
|
||||||
|
if len(residenceIDs) == 0 {
|
||||||
|
return map[uint]int{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
// Query to get overdue count grouped by residence
|
||||||
|
type result struct {
|
||||||
|
ResidenceID uint
|
||||||
|
Count int64
|
||||||
|
}
|
||||||
|
var results []result
|
||||||
|
|
||||||
|
err := r.db.Model(&models.Task{}).
|
||||||
|
Select("residence_id, COUNT(*) as count").
|
||||||
|
Scopes(task.ScopeForResidences(residenceIDs), task.ScopeOverdue(now)).
|
||||||
|
Group("residence_id").
|
||||||
|
Scan(&results).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to map
|
||||||
|
countMap := make(map[uint]int)
|
||||||
|
for _, r := range results {
|
||||||
|
countMap[r.ResidenceID] = int(r.Count)
|
||||||
|
}
|
||||||
|
|
||||||
|
return countMap, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -113,6 +113,16 @@ func (s *ResidenceService) GetMyResidences(userID uint) (*responses.MyResidences
|
|||||||
summary.TasksDueNextWeek = stats.TasksDueNextWeek
|
summary.TasksDueNextWeek = stats.TasksDueNextWeek
|
||||||
summary.TasksDueNextMonth = stats.TasksDueNextMonth
|
summary.TasksDueNextMonth = stats.TasksDueNextMonth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get per-residence overdue counts
|
||||||
|
overdueCounts, err := s.taskRepo.GetOverdueCountByResidence(residenceIDs)
|
||||||
|
if err == nil && overdueCounts != nil {
|
||||||
|
for i := range residenceResponses {
|
||||||
|
if count, ok := overdueCounts[residenceResponses[i].ID]; ok {
|
||||||
|
residenceResponses[i].OverdueCount = count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &responses.MyResidencesResponse{
|
return &responses.MyResidencesResponse{
|
||||||
|
|||||||
Reference in New Issue
Block a user