package models import ( "log" "time" "github.com/sujit-baniya/fiber-boilerplate/app" "gorm.io/gorm" ) type Contrato struct { gorm.Model ClienteID uint `json:"cliente_id" gorm:"column:cliente_id"` Cliente Cliente `json:"cliente" gorm:"foreignKey:ClienteID"` Servicios []Servicio `json:"servicios" gorm:"many2many:contrato_servicios"` FechaInicio time.Time `json:"fecha_inicio" gorm:"column:fecha_inicio"` FechaVencimiento time.Time `json:"fecha_vencimiento" gorm:"column:fecha_vencimiento"` PrecioAcordado float64 `json:"precio_acordado" gorm:"column:precio_acordado"` Estado string `json:"estado" gorm:"column:estado;default:'activo'"` // activo | vencido | cancelado | renovado AutoRenovar bool `json:"auto_renovar" gorm:"column:auto_renovar;default:false"` Notas string `json:"notas" gorm:"column:notas"` } func (Contrato) TableName() string { return "contratos" } func GetAllContratos(limit, offset int, search, estado string) ([]Contrato, int64, error) { var items []Contrato var total int64 db := app.Http.Database.DB.Model(&Contrato{}). Preload("Cliente").Preload("Servicios") if search != "" { db = db.Joins("JOIN clientes ON clientes.id = contratos.cliente_id"). Where("clientes.nombre ILIKE ? OR clientes.empresa ILIKE ?", "%"+search+"%", "%"+search+"%") } if estado != "" { db = db.Where("contratos.estado = ?", estado) } if err := db.Count(&total).Error; err != nil { return nil, 0, err } if err := db.Order("contratos.fecha_vencimiento ASC").Limit(limit).Offset(offset).Find(&items).Error; err != nil { return nil, 0, err } return items, total, nil } func GetContratoByID(id uint) (*Contrato, error) { var item Contrato if err := app.Http.Database.DB.Preload("Cliente").Preload("Servicios").First(&item, id).Error; err != nil { return nil, err } return &item, nil } // GetContratosByCliente devuelve contratos de un cliente específico func GetContratosByCliente(clienteID uint) ([]Contrato, error) { var items []Contrato if err := app.Http.Database.DB.Preload("Servicios"). Where("cliente_id = ?", clienteID). Order("fecha_vencimiento ASC").Find(&items).Error; err != nil { return nil, err } return items, nil } // GetContratosProximosVencer retorna contratos activos que vencen exactamente en N días func GetContratosProximosVencer(diasAntes int) ([]Contrato, error) { var items []Contrato target := time.Now().AddDate(0, 0, diasAntes).UTC() startOfDay := time.Date(target.Year(), target.Month(), target.Day(), 0, 0, 0, 0, time.UTC) endOfDay := startOfDay.Add(24 * time.Hour) if err := app.Http.Database.DB.Preload("Cliente").Preload("Servicios"). Where("estado = 'activo' AND fecha_vencimiento >= ? AND fecha_vencimiento < ?", startOfDay, endOfDay). Find(&items).Error; err != nil { log.Printf("Error getting contratos proximos: %v", err) return nil, err } return items, nil } // syncContratoServicios sincroniza la tabla join contrato_servicios usando SQL directo // para evitar que GORM intente hacer upsert de los servicios existentes. func syncContratoServicios(contratoID uint, servicioIDs []uint) error { db := app.Http.Database.DB if err := db.Exec("DELETE FROM contrato_servicios WHERE contrato_id = ?", contratoID).Error; err != nil { return err } for _, sid := range servicioIDs { if err := db.Exec( "INSERT INTO contrato_servicios (contrato_id, servicio_id) VALUES (?, ?) ON CONFLICT DO NOTHING", contratoID, sid, ).Error; err != nil { return err } } return nil } func CreateContrato(c Contrato, servicioIDs []uint) error { db := app.Http.Database.DB if err := db.Omit("Servicios.*").Create(&c).Error; err != nil { return err } return syncContratoServicios(c.ID, servicioIDs) } func UpdateContrato(c Contrato, servicioIDs []uint) error { db := app.Http.Database.DB if err := db.Model(&Contrato{}).Where("id = ?", c.ID).Updates(map[string]interface{}{ "cliente_id": c.ClienteID, "fecha_inicio": c.FechaInicio, "fecha_vencimiento": c.FechaVencimiento, "precio_acordado": c.PrecioAcordado, "estado": c.Estado, "auto_renovar": c.AutoRenovar, "notas": c.Notas, }).Error; err != nil { return err } if servicioIDs != nil { return syncContratoServicios(c.ID, servicioIDs) } return nil } func DeleteContrato(id uint) error { db := app.Http.Database.DB var c Contrato if err := db.First(&c, id).Error; err != nil { return err } db.Exec("DELETE FROM contrato_servicios WHERE contrato_id = ?", id) return db.Delete(&c).Error }