up
This commit is contained in:
+57
-33
@@ -20,13 +20,10 @@ type Contrato struct {
|
||||
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"`
|
||||
// Infraestructura asociada (opcional)
|
||||
ServidorID *uint `json:"servidor_id" gorm:"column:servidor_id"`
|
||||
Servidor Servidor `json:"servidor" gorm:"foreignKey:ServidorID"`
|
||||
ConxDbID *uint `json:"conx_db_id" gorm:"column:conx_db_id"`
|
||||
ConxDb ConxDb `json:"conx_db" gorm:"foreignKey:ConxDbID"`
|
||||
AiConfigID *uint `json:"ai_config_id" gorm:"column:ai_config_id"`
|
||||
AiConfig AiConfig `json:"ai_config" gorm:"foreignKey:AiConfigID"`
|
||||
// Infraestructura asociada (opcional, múltiple)
|
||||
Servidores []Servidor `json:"servidores" gorm:"many2many:contrato_servidores"`
|
||||
ConxDBs []ConxDb `json:"conx_dbs" gorm:"many2many:contrato_conx_dbs"`
|
||||
AiConfigs []AiConfig `json:"ai_configs" gorm:"many2many:contrato_ai_configs"`
|
||||
// Enlace de pago Bold: único por ciclo de pago.
|
||||
// Se reutiliza en múltiples notificaciones del mismo ciclo.
|
||||
// Se anula (vacía) cuando SALE_APPROVED llega y se registra el pago.
|
||||
@@ -44,7 +41,7 @@ func GetAllContratos(limit, offset int, search, estado string) ([]Contrato, int6
|
||||
var total int64
|
||||
db := app.Http.Database.DB.Model(&Contrato{}).
|
||||
Preload("Cliente").Preload("Servicios").
|
||||
Preload("Servidor").Preload("ConxDb").Preload("AiConfig")
|
||||
Preload("Servidores").Preload("ConxDBs").Preload("AiConfigs")
|
||||
if search != "" {
|
||||
db = db.Joins("JOIN clientes ON clientes.id = contratos.cliente_id").
|
||||
Where("clientes.nombre ILIKE ? OR clientes.empresa ILIKE ?",
|
||||
@@ -64,7 +61,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("Servicios").Preload("Servidor").Preload("ConxDb").Preload("AiConfig").First(&item, id).Error; err != nil {
|
||||
if err := app.Http.Database.DB.Preload("Cliente").Preload("Servicios").Preload("Servidores").Preload("ConxDBs").Preload("AiConfigs").First(&item, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
@@ -154,17 +151,40 @@ func syncContratoServicios(contratoID uint, servicioIDs []uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateContrato(c Contrato, servicioIDs []uint) error {
|
||||
db := app.Http.Database.DB
|
||||
if err := db.Omit("Servicios.*").Create(&c).Error; err != nil {
|
||||
func syncContratoJoin(db *gorm.DB, table string, contratoID uint, ids []uint) error {
|
||||
if err := db.Exec("DELETE FROM "+table+" WHERE contrato_id = ?", contratoID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return syncContratoServicios(c.ID, servicioIDs)
|
||||
for _, id := range ids {
|
||||
if err := db.Exec("INSERT INTO "+table+" (contrato_id, "+table[:len(table)-1]+"_id) VALUES (?, ?) ON CONFLICT DO NOTHING",
|
||||
contratoID, id,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateContrato(c Contrato, servicioIDs []uint) error {
|
||||
func CreateContrato(c Contrato, servicioIDs, servidorIDs, conxDBIDs, aiConfigIDs []uint) error {
|
||||
db := app.Http.Database.DB
|
||||
updates := map[string]interface{}{
|
||||
if err := db.Omit("Servicios.*", "Servidores.*", "ConxDBs.*", "AiConfigs.*").Create(&c).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := syncContratoServicios(c.ID, servicioIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := syncContratoJoin(db, "contrato_servidores", c.ID, servidorIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := syncContratoJoin(db, "contrato_conx_dbs", c.ID, conxDBIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
return syncContratoJoin(db, "contrato_ai_configs", c.ID, aiConfigIDs)
|
||||
}
|
||||
|
||||
func UpdateContrato(c Contrato, servicioIDs, servidorIDs, conxDBIDs, aiConfigIDs []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,
|
||||
@@ -173,27 +193,28 @@ func UpdateContrato(c Contrato, servicioIDs []uint) error {
|
||||
"estado": c.Estado,
|
||||
"auto_renovar": c.AutoRenovar,
|
||||
"notas": c.Notas,
|
||||
}
|
||||
if c.ServidorID != nil {
|
||||
updates["servidor_id"] = *c.ServidorID
|
||||
} else {
|
||||
updates["servidor_id"] = nil
|
||||
}
|
||||
if c.ConxDbID != nil {
|
||||
updates["conx_db_id"] = *c.ConxDbID
|
||||
} else {
|
||||
updates["conx_db_id"] = nil
|
||||
}
|
||||
if c.AiConfigID != nil {
|
||||
updates["ai_config_id"] = *c.AiConfigID
|
||||
} else {
|
||||
updates["ai_config_id"] = nil
|
||||
}
|
||||
if err := db.Model(&Contrato{}).Where("id = ?", c.ID).Updates(updates).Error; err != nil {
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if servicioIDs != nil {
|
||||
return syncContratoServicios(c.ID, servicioIDs)
|
||||
if err := syncContratoServicios(c.ID, servicioIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if servidorIDs != nil {
|
||||
if err := syncContratoJoin(db, "contrato_servidores", c.ID, servidorIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if conxDBIDs != nil {
|
||||
if err := syncContratoJoin(db, "contrato_conx_dbs", c.ID, conxDBIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if aiConfigIDs != nil {
|
||||
if err := syncContratoJoin(db, "contrato_ai_configs", c.ID, aiConfigIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -205,6 +226,9 @@ func DeleteContrato(id uint) error {
|
||||
return err
|
||||
}
|
||||
db.Exec("DELETE FROM contrato_servicios WHERE contrato_id = ?", id)
|
||||
db.Exec("DELETE FROM contrato_servidores WHERE contrato_id = ?", id)
|
||||
db.Exec("DELETE FROM contrato_conx_dbs WHERE contrato_id = ?", id)
|
||||
db.Exec("DELETE FROM contrato_ai_configs WHERE contrato_id = ?", id)
|
||||
return db.Delete(&c).Error
|
||||
}
|
||||
|
||||
|
||||
@@ -52,25 +52,25 @@
|
||||
</td>
|
||||
<td class="py-2 px-3">
|
||||
<div class="flex gap-1 flex-wrap">
|
||||
<template x-if="d.servidor">
|
||||
<span class="inline-flex items-center gap-0.5 text-xs bg-blue-50 text-blue-700 rounded px-1.5 py-0.5" title="Servidor: " + d.servidor.nombre>
|
||||
<template x-for="s in (d.servidores||[])" :key="'sv-'+s.ID">
|
||||
<span class="inline-flex items-center gap-0.5 text-xs bg-blue-50 text-blue-700 rounded px-1.5 py-0.5" :title="'Servidor: '+s.nombre">
|
||||
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.75L4.5 7.5l.75-1.5m-2.25 6.75h16.5"/></svg>
|
||||
<span x-text="d.servidor.nombre"></span>
|
||||
<span x-text="s.nombre"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="d.conx_db">
|
||||
<span class="inline-flex items-center gap-0.5 text-xs bg-green-50 text-green-700 rounded px-1.5 py-0.5" title="BD: " + d.conx_db.nombre>
|
||||
<template x-for="d in (d.conx_dbs||[])" :key="'db-'+d.ID">
|
||||
<span class="inline-flex items-center gap-0.5 text-xs bg-green-50 text-green-700 rounded px-1.5 py-0.5" :title="'BD: '+d.nombre">
|
||||
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"/></svg>
|
||||
<span x-text="d.conx_db.nombre"></span>
|
||||
<span x-text="d.nombre"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="d.ai_config">
|
||||
<span class="inline-flex items-center gap-0.5 text-xs bg-purple-50 text-purple-700 rounded px-1.5 py-0.5" title="IA: " + d.ai_config.nombre>
|
||||
<template x-for="a in (d.ai_configs||[])" :key="'ai-'+a.ID">
|
||||
<span class="inline-flex items-center gap-0.5 text-xs bg-purple-50 text-purple-700 rounded px-1.5 py-0.5" :title="'IA: '+a.nombre">
|
||||
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 0 0-2.455 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z"/></svg>
|
||||
<span x-text="d.ai_config.nombre"></span>
|
||||
<span x-text="a.nombre"></span>
|
||||
</span>
|
||||
</template>
|
||||
<span x-show="!d.servidor && !d.conx_db && !d.ai_config" class="text-gray-300">—</span>
|
||||
<span x-show="!(d.servidores||[]).length && !(d.conx_dbs||[]).length && !(d.ai_configs||[]).length" class="text-gray-300">—</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-2 px-3" x-text="fmtDate(d.fecha_vencimiento)"></td>
|
||||
@@ -211,35 +211,51 @@
|
||||
<textarea x-model="form.notas" rows="2" class="mt-1 w-full border rounded px-3 py-2 text-sm"></textarea>
|
||||
</div>
|
||||
<div class="col-span-2 border-t border-gray-100 pt-3 mt-1">
|
||||
<p class="text-xs font-semibold text-gray-500 mb-2 uppercase tracking-wider">Infraestructura <span class="font-normal normal-case text-gray-400">(opcional)</span></p>
|
||||
</div>
|
||||
<div class="col-span-2 sm:col-span-1">
|
||||
<label class="text-xs font-medium text-gray-600">Servidor</label>
|
||||
<select x-model="form.servidor_id" @change="onServidorChange()" class="mt-1 w-full border rounded px-3 py-2 text-sm">
|
||||
<option value="">— Sin servidor —</option>
|
||||
<template x-for="s in servidoresList" :key="s.ID">
|
||||
<option :value="s.ID" x-text="s.nombre + ' — ' + (s.ip_servidor||'')"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-span-2 sm:col-span-1">
|
||||
<label class="text-xs font-medium text-gray-600">Base de datos</label>
|
||||
<select x-model="form.conx_db_id" class="mt-1 w-full border rounded px-3 py-2 text-sm">
|
||||
<option value="">— Sin BD —</option>
|
||||
<template x-for="db in conxDbList" :key="db.ID">
|
||||
<option :value="db.ID" x-text="db.nombre"></option>
|
||||
</template>
|
||||
</select>
|
||||
<p x-show="form.servidor_id && conxDbList.length===0" class="text-xs text-gray-400 mt-0.5">Sin BD para este servidor</p>
|
||||
<p class="text-xs font-semibold text-gray-500 mb-2 uppercase tracking-wider">Infraestructura <span class="font-normal normal-case text-gray-400">(opcional, múltiple)</span></p>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="text-xs font-medium text-gray-600">IA / AI Config</label>
|
||||
<select x-model="form.ai_config_id" class="mt-1 w-full border rounded px-3 py-2 text-sm">
|
||||
<option value="">— Sin IA —</option>
|
||||
<template x-for="ai in aiConfigList" :key="ai.ID">
|
||||
<option :value="ai.ID" x-text="ai.nombre + ' (' + ai.provider + ')'"></option>
|
||||
<label class="text-xs font-medium text-gray-600 mb-1 block">Servidores</label>
|
||||
<div class="border rounded max-h-32 overflow-y-auto divide-y text-sm">
|
||||
<template x-for="s in servidoresList" :key="s.ID">
|
||||
<label class="flex items-center gap-2 px-3 py-1.5 hover:bg-gray-50 cursor-pointer">
|
||||
<input type="checkbox" :value="s.ID"
|
||||
:checked="form.servidor_ids.includes(s.ID)"
|
||||
@change="toggleServidor(s.ID)" />
|
||||
<span x-text="s.nombre + ' — ' + (s.ip_servidor||'')"></span>
|
||||
</label>
|
||||
</template>
|
||||
</select>
|
||||
<div x-show="servidoresList.length===0" class="text-xs text-gray-400 px-3 py-2">Sin servidores</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="text-xs font-medium text-gray-600 mb-1 block">Bases de datos <span class="font-normal text-gray-400">(filtradas por servidor seleccionado)</span></label>
|
||||
<div class="border rounded max-h-32 overflow-y-auto divide-y text-sm">
|
||||
<template x-for="db in conxDbList" :key="db.ID">
|
||||
<label class="flex items-center gap-2 px-3 py-1.5 hover:bg-gray-50 cursor-pointer">
|
||||
<input type="checkbox" :value="db.ID"
|
||||
:checked="form.conx_db_ids.includes(db.ID)"
|
||||
@change="toggleConxDb(db.ID)" />
|
||||
<span x-text="db.nombre"></span>
|
||||
</label>
|
||||
</template>
|
||||
<div x-show="conxDbList.length===0" class="text-xs text-gray-400 px-3 py-2">
|
||||
<span x-text="form.servidor_ids.length > 0 ? 'Sin BD para los servidores seleccionados' : 'Selecciona un servidor primero'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="text-xs font-medium text-gray-600 mb-1 block">IA / AI Configs</label>
|
||||
<div class="border rounded max-h-32 overflow-y-auto divide-y text-sm">
|
||||
<template x-for="ai in aiConfigList" :key="ai.ID">
|
||||
<label class="flex items-center gap-2 px-3 py-1.5 hover:bg-gray-50 cursor-pointer">
|
||||
<input type="checkbox" :value="ai.ID"
|
||||
:checked="form.ai_config_ids.includes(ai.ID)"
|
||||
@change="toggleAiConfig(ai.ID)" />
|
||||
<span x-text="ai.nombre + ' (' + ai.provider + ')'"></span>
|
||||
</label>
|
||||
</template>
|
||||
<div x-show="aiConfigList.length===0" class="text-xs text-gray-400 px-3 py-2">Sin configs de IA</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" x-model="form.auto_renovar" id="auto_ren" />
|
||||
@@ -407,7 +423,7 @@ document.addEventListener('alpine:init', () => {
|
||||
servidoresList: [], conxDbList: [], aiConfigList: [],
|
||||
selectedId: null,
|
||||
verificandoID: null,
|
||||
form: { cliente_id:'', servicio_ids:[], fecha_inicio:'', fecha_vencimiento:'', precio_acordado:0, moneda:'COP', estado:'activo', auto_renovar:false, notas:'', servidor_id:'', conx_db_id:'', ai_config_id:'' },
|
||||
form: { cliente_id:'', servicio_ids:[], fecha_inicio:'', fecha_vencimiento:'', precio_acordado:0, moneda:'COP', estado:'activo', auto_renovar:false, notas:'', servidor_ids:[], conx_db_ids:[], ai_config_ids:[] },
|
||||
toast: { show: false, msg: '', type: 'ok' },
|
||||
|
||||
async init() {
|
||||
@@ -425,12 +441,13 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
async loadSelects() {
|
||||
try {
|
||||
const [c, s, r, sv, ai] = await Promise.all([
|
||||
const [c, s, r, sv, ai, db] = await Promise.all([
|
||||
axios.get('/app/api/clientes/select'),
|
||||
axios.get('/app/api/servicios/select'),
|
||||
axios.get('/app/api/reglas-notificacion'),
|
||||
axios.get('/app/loadservidorselect'),
|
||||
axios.get('/app/api/ai-config/select'),
|
||||
axios.get('/app/api/conxdb/select'),
|
||||
]);
|
||||
this.clientes = Array.isArray(c.data) ? c.data : (c.data.registros || []);
|
||||
this.servicios = Array.isArray(s.data) ? s.data : (s.data.registros || []);
|
||||
@@ -440,20 +457,23 @@ document.addEventListener('alpine:init', () => {
|
||||
if (this.reglasBienvenida.length > 0) this.bienvenidaReglaId = this.reglasBienvenida[0].ID;
|
||||
this.servidoresList = sv.data.registros || [];
|
||||
this.aiConfigList = ai.data.registros || [];
|
||||
this.conxDbList = db.data.registros || [];
|
||||
} catch(e) {
|
||||
this.showToast('Error cargando listas: ' + (e.response?.data?.error || e.message), 'error');
|
||||
}
|
||||
},
|
||||
|
||||
async onServidorChange() {
|
||||
this.form.conx_db_id = '';
|
||||
if (!this.form.servidor_id) { this.conxDbList = []; return; }
|
||||
try {
|
||||
const res = await axios.get('/app/api/conxdb/select', { params: { servidor_id: this.form.servidor_id } });
|
||||
this.conxDbList = res.data.registros || [];
|
||||
} catch(e) {
|
||||
this.conxDbList = [];
|
||||
}
|
||||
toggleServidor(id) {
|
||||
const idx = (this.form.servidor_ids || []).indexOf(id);
|
||||
if (idx === -1) { this.form.servidor_ids.push(id); } else { this.form.servidor_ids.splice(idx, 1); }
|
||||
},
|
||||
toggleConxDb(id) {
|
||||
const idx = (this.form.conx_db_ids || []).indexOf(id);
|
||||
if (idx === -1) { this.form.conx_db_ids.push(id); } else { this.form.conx_db_ids.splice(idx, 1); }
|
||||
},
|
||||
toggleAiConfig(id) {
|
||||
const idx = (this.form.ai_config_ids || []).indexOf(id);
|
||||
if (idx === -1) { this.form.ai_config_ids.push(id); } else { this.form.ai_config_ids.splice(idx, 1); }
|
||||
},
|
||||
|
||||
get precioSugerido() {
|
||||
@@ -482,23 +502,18 @@ document.addEventListener('alpine:init', () => {
|
||||
estado: d.estado,
|
||||
auto_renovar: d.auto_renovar,
|
||||
notas: d.notas,
|
||||
servidor_id: d.servidor_id || '',
|
||||
conx_db_id: d.conx_db_id || '',
|
||||
ai_config_id: d.ai_config_id || ''
|
||||
servidor_ids: (d.servidores || []).map(s => s.ID),
|
||||
conx_db_ids: (d.conx_dbs || []).map(s => s.ID),
|
||||
ai_config_ids: (d.ai_configs || []).map(s => s.ID)
|
||||
};
|
||||
this.selectedId = d.ID;
|
||||
this.editModal = true;
|
||||
// Cargar BD del servidor seleccionado
|
||||
if (d.servidor_id) {
|
||||
await this.onServidorChange();
|
||||
}
|
||||
},
|
||||
openDelete(d) { this.selectedId = d.ID; this.deleteModal = true; },
|
||||
closeModals() {
|
||||
this.addModal = this.editModal = this.deleteModal = false;
|
||||
this.selectedId = null;
|
||||
this.conxDbList = [];
|
||||
this.form = { cliente_id:'', servicio_ids:[], fecha_inicio:'', fecha_vencimiento:'', precio_acordado:0, moneda:'COP', estado:'activo', auto_renovar:false, notas:'', servidor_id:'', conx_db_id:'', ai_config_id:'' };
|
||||
this.form = { cliente_id:'', servicio_ids:[], fecha_inicio:'', fecha_vencimiento:'', precio_acordado:0, moneda:'COP', estado:'activo', auto_renovar:false, notas:'', servidor_ids:[], conx_db_ids:[], ai_config_ids:[] };
|
||||
},
|
||||
|
||||
async save() {
|
||||
@@ -509,9 +524,9 @@ document.addEventListener('alpine:init', () => {
|
||||
cliente_id: parseInt(this.form.cliente_id) || 0,
|
||||
servicio_id: parseInt(this.form.servicio_id) || 0,
|
||||
precio_acordado: parseFloat(this.form.precio_acordado) || 0,
|
||||
servidor_id: this.form.servidor_id ? parseInt(this.form.servidor_id) : null,
|
||||
conx_db_id: this.form.conx_db_id ? parseInt(this.form.conx_db_id) : null,
|
||||
ai_config_id: this.form.ai_config_id ? parseInt(this.form.ai_config_id) : null,
|
||||
servidor_ids: this.form.servidor_ids || [],
|
||||
conx_db_ids: this.form.conx_db_ids || [],
|
||||
ai_config_ids: this.form.ai_config_ids || [],
|
||||
};
|
||||
if (this.editModal) {
|
||||
await axios.put(`/app/api/contratos/${this.selectedId}`, payload);
|
||||
|
||||
@@ -62,16 +62,16 @@ func GetContratos(c *fiber.Ctx) error {
|
||||
|
||||
func CreateContrato(c *fiber.Ctx) error {
|
||||
type Input struct {
|
||||
ClienteID uint `json:"cliente_id"`
|
||||
ServicioIDs []uint `json:"servicio_ids"`
|
||||
FechaInicio string `json:"fecha_inicio"`
|
||||
FechaVencimiento string `json:"fecha_vencimiento"`
|
||||
ClienteID uint `json:"cliente_id"`
|
||||
ServicioIDs []uint `json:"servicio_ids"`
|
||||
FechaInicio string `json:"fecha_inicio"`
|
||||
FechaVencimiento string `json:"fecha_vencimiento"`
|
||||
PrecioAcordado float64 `json:"precio_acordado"`
|
||||
AutoRenovar bool `json:"auto_renovar"`
|
||||
Notas string `json:"notas"`
|
||||
ServidorID *uint `json:"servidor_id"`
|
||||
ConxDbID *uint `json:"conx_db_id"`
|
||||
AiConfigID *uint `json:"ai_config_id"`
|
||||
AutoRenovar bool `json:"auto_renovar"`
|
||||
Notas string `json:"notas"`
|
||||
ServidorIDs []uint `json:"servidor_ids"`
|
||||
ConxDbIDs []uint `json:"conx_db_ids"`
|
||||
AiConfigIDs []uint `json:"ai_config_ids"`
|
||||
}
|
||||
var inp Input
|
||||
if err := c.BodyParser(&inp); err != nil {
|
||||
@@ -93,11 +93,17 @@ func CreateContrato(c *fiber.Ctx) error {
|
||||
AutoRenovar: inp.AutoRenovar,
|
||||
Notas: inp.Notas,
|
||||
Estado: "activo",
|
||||
ServidorID: inp.ServidorID,
|
||||
ConxDbID: inp.ConxDbID,
|
||||
AiConfigID: inp.AiConfigID,
|
||||
}
|
||||
if err := models.CreateContrato(m, inp.ServicioIDs); err != nil {
|
||||
if inp.ServidorIDs == nil {
|
||||
inp.ServidorIDs = []uint{}
|
||||
}
|
||||
if inp.ConxDbIDs == nil {
|
||||
inp.ConxDbIDs = []uint{}
|
||||
}
|
||||
if inp.AiConfigIDs == nil {
|
||||
inp.AiConfigIDs = []uint{}
|
||||
}
|
||||
if err := models.CreateContrato(m, inp.ServicioIDs, inp.ServidorIDs, inp.ConxDbIDs, inp.AiConfigIDs); err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
// Devolver el ID del contrato creado para que el frontend pueda enviar bienvenida
|
||||
@@ -121,9 +127,9 @@ func UpdateContrato(c *fiber.Ctx) error {
|
||||
PrecioAcordado float64 `json:"precio_acordado"`
|
||||
AutoRenovar bool `json:"auto_renovar"`
|
||||
Notas string `json:"notas"`
|
||||
ServidorID *uint `json:"servidor_id"`
|
||||
ConxDbID *uint `json:"conx_db_id"`
|
||||
AiConfigID *uint `json:"ai_config_id"`
|
||||
ServidorIDs []uint `json:"servidor_ids"`
|
||||
ConxDbIDs []uint `json:"conx_db_ids"`
|
||||
AiConfigIDs []uint `json:"ai_config_ids"`
|
||||
}
|
||||
var inp Input
|
||||
if err := c.BodyParser(&inp); err != nil {
|
||||
@@ -145,10 +151,7 @@ func UpdateContrato(c *fiber.Ctx) error {
|
||||
existing.PrecioAcordado = inp.PrecioAcordado
|
||||
existing.AutoRenovar = inp.AutoRenovar
|
||||
existing.Notas = inp.Notas
|
||||
existing.ServidorID = inp.ServidorID
|
||||
existing.ConxDbID = inp.ConxDbID
|
||||
existing.AiConfigID = inp.AiConfigID
|
||||
if err := models.UpdateContrato(*existing, inp.ServicioIDs); err != nil {
|
||||
if err := models.UpdateContrato(*existing, inp.ServicioIDs, inp.ServidorIDs, inp.ConxDbIDs, inp.AiConfigIDs); err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.JSON(fiber.Map{"message": "Actualizado", "ok": true})
|
||||
@@ -185,12 +188,12 @@ func RenovarContrato(c *fiber.Ctx) error {
|
||||
Estado: "activo",
|
||||
Notas: "Renovación automática desde contrato #" + strconv.Itoa(int(existing.ID)),
|
||||
}
|
||||
if err := models.CreateContrato(nuevo, servicioIDs); err != nil {
|
||||
if err := models.CreateContrato(nuevo, servicioIDs, nil, nil, nil); err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
// Marcar anterior como renovado
|
||||
existing.Estado = "renovado"
|
||||
models.UpdateContrato(*existing, nil)
|
||||
models.UpdateContrato(*existing, nil, nil, nil, nil)
|
||||
|
||||
return c.JSON(fiber.Map{"message": "Renovado", "ok": true})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user