Files
honeyDueAPI/internal/repositories/contractor_repo.go
Trey t 4e9b31377b Make contractor residence optional with visibility rules
- Make residence_id nullable in contractor model
- Add created_by_id field to track contractor creator
- Update access control: personal contractors visible only to creator,
  residence contractors visible to all residence users
- Add database migration for schema changes
- Update admin panel DTOs and handlers for optional residence
- Fix test utilities for new model structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 18:42:11 -06:00

166 lines
4.9 KiB
Go

package repositories
import (
"gorm.io/gorm"
"github.com/treytartt/casera-api/internal/models"
)
// ContractorRepository handles database operations for contractors
type ContractorRepository struct {
db *gorm.DB
}
// NewContractorRepository creates a new contractor repository
func NewContractorRepository(db *gorm.DB) *ContractorRepository {
return &ContractorRepository{db: db}
}
// FindByID finds a contractor by ID with preloaded relations
func (r *ContractorRepository) FindByID(id uint) (*models.Contractor, error) {
var contractor models.Contractor
err := r.db.Preload("CreatedBy").
Preload("Specialties").
Preload("Tasks").
Where("id = ? AND is_active = ?", id, true).
First(&contractor).Error
if err != nil {
return nil, err
}
return &contractor, nil
}
// FindByResidence finds all contractors for a residence
func (r *ContractorRepository) FindByResidence(residenceID uint) ([]models.Contractor, error) {
var contractors []models.Contractor
err := r.db.Preload("CreatedBy").
Preload("Specialties").
Where("residence_id = ? AND is_active = ?", residenceID, true).
Order("is_favorite DESC, name ASC").
Find(&contractors).Error
return contractors, err
}
// FindByUser finds all contractors accessible to a user
// Returns contractors that either:
// 1. Have no residence (personal contractors) AND were created by the user
// 2. Belong to a residence the user has access to
func (r *ContractorRepository) FindByUser(userID uint, residenceIDs []uint) ([]models.Contractor, error) {
var contractors []models.Contractor
query := r.db.Preload("CreatedBy").
Preload("Specialties").
Preload("Residence").
Where("is_active = ?", true)
if len(residenceIDs) > 0 {
// Personal contractors (no residence, created by user) OR residence contractors
query = query.Where(
"(residence_id IS NULL AND created_by_id = ?) OR (residence_id IN ?)",
userID, residenceIDs,
)
} else {
// Only personal contractors
query = query.Where("residence_id IS NULL AND created_by_id = ?", userID)
}
err := query.Order("is_favorite DESC, name ASC").Find(&contractors).Error
return contractors, err
}
// Create creates a new contractor
func (r *ContractorRepository) Create(contractor *models.Contractor) error {
return r.db.Create(contractor).Error
}
// Update updates a contractor
func (r *ContractorRepository) Update(contractor *models.Contractor) error {
return r.db.Save(contractor).Error
}
// Delete soft-deletes a contractor
func (r *ContractorRepository) Delete(id uint) error {
return r.db.Model(&models.Contractor{}).
Where("id = ?", id).
Update("is_active", false).Error
}
// ToggleFavorite toggles the favorite status of a contractor
func (r *ContractorRepository) ToggleFavorite(id uint) (bool, error) {
var contractor models.Contractor
if err := r.db.First(&contractor, id).Error; err != nil {
return false, err
}
newStatus := !contractor.IsFavorite
err := r.db.Model(&models.Contractor{}).
Where("id = ?", id).
Update("is_favorite", newStatus).Error
return newStatus, err
}
// GetTasksForContractor gets all tasks associated with a contractor
func (r *ContractorRepository) GetTasksForContractor(contractorID uint) ([]models.Task, error) {
var tasks []models.Task
err := r.db.Preload("Category").
Preload("Priority").
Preload("Status").
Where("contractor_id = ?", contractorID).
Order("due_date ASC NULLS LAST").
Find(&tasks).Error
return tasks, err
}
// SetSpecialties sets the specialties for a contractor
func (r *ContractorRepository) SetSpecialties(contractorID uint, specialtyIDs []uint) error {
var contractor models.Contractor
if err := r.db.First(&contractor, contractorID).Error; err != nil {
return err
}
// Clear existing specialties
if err := r.db.Model(&contractor).Association("Specialties").Clear(); err != nil {
return err
}
if len(specialtyIDs) == 0 {
return nil
}
// Add new specialties
var specialties []models.ContractorSpecialty
if err := r.db.Where("id IN ?", specialtyIDs).Find(&specialties).Error; err != nil {
return err
}
return r.db.Model(&contractor).Association("Specialties").Append(specialties)
}
// CountByResidence counts contractors in a residence
func (r *ContractorRepository) CountByResidence(residenceID uint) (int64, error) {
var count int64
err := r.db.Model(&models.Contractor{}).
Where("residence_id = ? AND is_active = ?", residenceID, true).
Count(&count).Error
return count, err
}
// === Specialty Operations ===
// GetAllSpecialties returns all contractor specialties
func (r *ContractorRepository) GetAllSpecialties() ([]models.ContractorSpecialty, error) {
var specialties []models.ContractorSpecialty
err := r.db.Order("display_order, name").Find(&specialties).Error
return specialties, err
}
// FindSpecialtyByID finds a specialty by ID
func (r *ContractorRepository) FindSpecialtyByID(id uint) (*models.ContractorSpecialty, error) {
var specialty models.ContractorSpecialty
err := r.db.First(&specialty, id).Error
if err != nil {
return nil, err
}
return &specialty, nil
}