feat: contrato soporta múltiples servicios (many2many) con precio sugerido

This commit is contained in:
Lizandro Guarnizo
2026-04-30 22:15:31 -05:00
parent 1b0374204c
commit e751a9f894
5 changed files with 157 additions and 76 deletions
+41 -20
View File
@@ -10,16 +10,15 @@ import (
type Contrato struct {
gorm.Model
ClienteID uint `json:"cliente_id" gorm:"column:cliente_id"`
Cliente Cliente `json:"cliente" gorm:"foreignKey:ClienteID"`
ServicioID uint `json:"servicio_id" gorm:"column:servicio_id"`
Servicio Servicio `json:"servicio" gorm:"foreignKey:ServicioID"`
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"`
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" }
@@ -28,7 +27,7 @@ func GetAllContratos(limit, offset int, search, estado string) ([]Contrato, int6
var items []Contrato
var total int64
db := app.Http.Database.DB.Model(&Contrato{}).
Preload("Cliente").Preload("Servicio")
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 ?",
@@ -48,7 +47,7 @@ func GetAllContratos(limit, offset int, search, estado string) ([]Contrato, int6
func GetContratoByID(id uint) (*Contrato, error) {
var item Contrato
if err := app.Http.Database.DB.Preload("Cliente").Preload("Servicio").First(&item, id).Error; err != nil {
if err := app.Http.Database.DB.Preload("Cliente").Preload("Servicios").First(&item, id).Error; err != nil {
return nil, err
}
return &item, nil
@@ -57,7 +56,7 @@ func GetContratoByID(id uint) (*Contrato, error) {
// GetContratosByCliente devuelve contratos de un cliente específico
func GetContratosByCliente(clienteID uint) ([]Contrato, error) {
var items []Contrato
if err := app.Http.Database.DB.Preload("Servicio").
if err := app.Http.Database.DB.Preload("Servicios").
Where("cliente_id = ?", clienteID).
Order("fecha_vencimiento ASC").Find(&items).Error; err != nil {
return nil, err
@@ -72,7 +71,7 @@ func GetContratosProximosVencer(diasAntes int) ([]Contrato, error) {
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("Servicio").
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)
@@ -81,21 +80,42 @@ func GetContratosProximosVencer(diasAntes int) ([]Contrato, error) {
return items, nil
}
func CreateContrato(c Contrato) error {
return app.Http.Database.DB.Create(&c).Error
func CreateContrato(c Contrato, servicioIDs []uint) error {
db := app.Http.Database.DB
if err := db.Create(&c).Error; err != nil {
return err
}
if len(servicioIDs) > 0 {
var servicios []Servicio
if err := db.Find(&servicios, servicioIDs).Error; err != nil {
return err
}
return db.Model(&c).Association("Servicios").Replace(servicios)
}
return nil
}
func UpdateContrato(c Contrato) error {
return app.Http.Database.DB.Model(&Contrato{}).Where("id = ?", c.ID).Updates(map[string]interface{}{
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,
"servicio_id": c.ServicioID,
"fecha_inicio": c.FechaInicio,
"fecha_vencimiento": c.FechaVencimiento,
"precio_acordado": c.PrecioAcordado,
"estado": c.Estado,
"auto_renovar": c.AutoRenovar,
"notas": c.Notas,
}).Error
}).Error; err != nil {
return err
}
if servicioIDs != nil {
var servicios []Servicio
if err := db.Find(&servicios, servicioIDs).Error; err != nil {
return err
}
return db.Model(&Contrato{Model: gorm.Model{ID: c.ID}}).Association("Servicios").Replace(servicios)
}
return nil
}
func DeleteContrato(id uint) error {
@@ -103,5 +123,6 @@ func DeleteContrato(id uint) error {
if err := app.Http.Database.DB.First(&c, id).Error; err != nil {
return err
}
app.Http.Database.DB.Model(&c).Association("Servicios").Clear()
return app.Http.Database.DB.Delete(&c).Error
}