- Add document list filter support (residence, type, category, contractor, is_active, expiring_soon, search) to handler/service/repo - Add `days` query param parsing to ListTasks handler (matches ListTasksByResidence) - Add `error.invalid_token` i18n key to all 9 non-English locale files - Update contract test to include VerificationResponse mapping Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
208 lines
6.5 KiB
Go
208 lines
6.5 KiB
Go
package repositories
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/treytartt/casera-api/internal/models"
|
|
)
|
|
|
|
// DocumentFilter contains optional filter parameters for listing documents.
|
|
type DocumentFilter struct {
|
|
ResidenceID *uint
|
|
DocumentType string
|
|
Category string
|
|
ContractorID *uint
|
|
IsActive *bool
|
|
ExpiringSoon *int
|
|
Tags string
|
|
Search string
|
|
}
|
|
|
|
// DocumentRepository handles database operations for documents
|
|
type DocumentRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewDocumentRepository creates a new document repository
|
|
func NewDocumentRepository(db *gorm.DB) *DocumentRepository {
|
|
return &DocumentRepository{db: db}
|
|
}
|
|
|
|
// FindByID finds a document by ID with preloaded relations
|
|
func (r *DocumentRepository) FindByID(id uint) (*models.Document, error) {
|
|
var document models.Document
|
|
err := r.db.Preload("CreatedBy").
|
|
Preload("Task").
|
|
Preload("Images").
|
|
Where("id = ? AND is_active = ?", id, true).
|
|
First(&document).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &document, nil
|
|
}
|
|
|
|
// FindByResidence finds all documents for a residence
|
|
func (r *DocumentRepository) FindByResidence(residenceID uint) ([]models.Document, error) {
|
|
var documents []models.Document
|
|
err := r.db.Preload("CreatedBy").
|
|
Preload("Images").
|
|
Where("residence_id = ? AND is_active = ?", residenceID, true).
|
|
Order("created_at DESC").
|
|
Find(&documents).Error
|
|
return documents, err
|
|
}
|
|
|
|
// FindByUser finds all documents accessible to a user
|
|
func (r *DocumentRepository) FindByUser(residenceIDs []uint) ([]models.Document, error) {
|
|
var documents []models.Document
|
|
err := r.db.Preload("CreatedBy").
|
|
Preload("Residence").
|
|
Preload("Images").
|
|
Where("residence_id IN ? AND is_active = ?", residenceIDs, true).
|
|
Order("created_at DESC").
|
|
Find(&documents).Error
|
|
return documents, err
|
|
}
|
|
|
|
// FindByUserFiltered finds documents accessible to a user with optional filters.
|
|
func (r *DocumentRepository) FindByUserFiltered(residenceIDs []uint, filter *DocumentFilter) ([]models.Document, error) {
|
|
query := r.db.Preload("CreatedBy").
|
|
Preload("Residence").
|
|
Preload("Images").
|
|
Where("residence_id IN ? AND is_active = ?", residenceIDs, true)
|
|
|
|
if filter != nil {
|
|
if filter.DocumentType != "" {
|
|
query = query.Where("document_type = ?", filter.DocumentType)
|
|
}
|
|
if filter.Category != "" {
|
|
query = query.Where("category = ?", filter.Category)
|
|
}
|
|
if filter.ContractorID != nil {
|
|
query = query.Where("contractor_id = ?", *filter.ContractorID)
|
|
}
|
|
if filter.IsActive != nil {
|
|
query = query.Where("is_active = ?", *filter.IsActive)
|
|
}
|
|
if filter.ExpiringSoon != nil {
|
|
now := time.Now().UTC()
|
|
threshold := now.AddDate(0, 0, *filter.ExpiringSoon)
|
|
query = query.Where("expiry_date IS NOT NULL AND expiry_date > ? AND expiry_date <= ?", now, threshold)
|
|
}
|
|
if filter.Tags != "" {
|
|
query = query.Where("tags LIKE ?", "%"+filter.Tags+"%")
|
|
}
|
|
if filter.Search != "" {
|
|
searchPattern := "%" + filter.Search + "%"
|
|
query = query.Where("(title ILIKE ? OR description ILIKE ?)", searchPattern, searchPattern)
|
|
}
|
|
}
|
|
|
|
var documents []models.Document
|
|
err := query.Order("created_at DESC").Find(&documents).Error
|
|
return documents, err
|
|
}
|
|
|
|
// FindWarranties finds all warranty documents
|
|
func (r *DocumentRepository) FindWarranties(residenceIDs []uint) ([]models.Document, error) {
|
|
var documents []models.Document
|
|
err := r.db.Preload("CreatedBy").
|
|
Preload("Residence").
|
|
Preload("Images").
|
|
Where("residence_id IN ? AND is_active = ? AND document_type = ?",
|
|
residenceIDs, true, models.DocumentTypeWarranty).
|
|
Order("expiry_date ASC NULLS LAST").
|
|
Find(&documents).Error
|
|
return documents, err
|
|
}
|
|
|
|
// FindExpiringWarranties finds warranties expiring within the specified days
|
|
func (r *DocumentRepository) FindExpiringWarranties(residenceIDs []uint, days int) ([]models.Document, error) {
|
|
threshold := time.Now().UTC().AddDate(0, 0, days)
|
|
now := time.Now().UTC()
|
|
|
|
var documents []models.Document
|
|
err := r.db.Preload("CreatedBy").
|
|
Preload("Residence").
|
|
Preload("Images").
|
|
Where("residence_id IN ? AND is_active = ? AND document_type = ? AND expiry_date > ? AND expiry_date <= ?",
|
|
residenceIDs, true, models.DocumentTypeWarranty, now, threshold).
|
|
Order("expiry_date ASC").
|
|
Find(&documents).Error
|
|
return documents, err
|
|
}
|
|
|
|
// Create creates a new document
|
|
func (r *DocumentRepository) Create(document *models.Document) error {
|
|
return r.db.Create(document).Error
|
|
}
|
|
|
|
// Update updates a document
|
|
// Uses Omit to exclude associations that could interfere with Save
|
|
func (r *DocumentRepository) Update(document *models.Document) error {
|
|
return r.db.Omit("CreatedBy", "Task", "Images", "Residence").Save(document).Error
|
|
}
|
|
|
|
// Delete soft-deletes a document
|
|
func (r *DocumentRepository) Delete(id uint) error {
|
|
return r.db.Model(&models.Document{}).
|
|
Where("id = ?", id).
|
|
Update("is_active", false).Error
|
|
}
|
|
|
|
// Activate activates a document
|
|
func (r *DocumentRepository) Activate(id uint) error {
|
|
return r.db.Model(&models.Document{}).
|
|
Where("id = ?", id).
|
|
Update("is_active", true).Error
|
|
}
|
|
|
|
// Deactivate deactivates a document
|
|
func (r *DocumentRepository) Deactivate(id uint) error {
|
|
return r.db.Model(&models.Document{}).
|
|
Where("id = ?", id).
|
|
Update("is_active", false).Error
|
|
}
|
|
|
|
// CountByResidence counts documents in a residence
|
|
func (r *DocumentRepository) CountByResidence(residenceID uint) (int64, error) {
|
|
var count int64
|
|
err := r.db.Model(&models.Document{}).
|
|
Where("residence_id = ? AND is_active = ?", residenceID, true).
|
|
Count(&count).Error
|
|
return count, err
|
|
}
|
|
|
|
// FindByIDIncludingInactive finds a document by ID including inactive ones
|
|
func (r *DocumentRepository) FindByIDIncludingInactive(id uint, document *models.Document) error {
|
|
return r.db.Preload("CreatedBy").Preload("Images").First(document, id).Error
|
|
}
|
|
|
|
// CreateDocumentImage creates a new document image
|
|
func (r *DocumentRepository) CreateDocumentImage(image *models.DocumentImage) error {
|
|
return r.db.Create(image).Error
|
|
}
|
|
|
|
// DeleteDocumentImage deletes a document image
|
|
func (r *DocumentRepository) DeleteDocumentImage(id uint) error {
|
|
return r.db.Delete(&models.DocumentImage{}, id).Error
|
|
}
|
|
|
|
// DeleteDocumentImages deletes all images for a document
|
|
func (r *DocumentRepository) DeleteDocumentImages(documentID uint) error {
|
|
return r.db.Where("document_id = ?", documentID).Delete(&models.DocumentImage{}).Error
|
|
}
|
|
|
|
// FindImageByID finds a document image by ID
|
|
func (r *DocumentRepository) FindImageByID(id uint) (*models.DocumentImage, error) {
|
|
var image models.DocumentImage
|
|
err := r.db.First(&image, id).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &image, nil
|
|
}
|