feat: cuentas por cobrar usa listado de clientes en vez de entidades contables

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro GD
2026-07-13 02:44:19 +00:00
co-authored by Claude Sonnet 5
parent daf0b2c2a1
commit 6e281dfbda
4 changed files with 35 additions and 36 deletions
+9 -10
View File
@@ -61,8 +61,10 @@ func (Transaccion) TableName() string { return "contab_transacciones" }
type CuentaCobro struct {
gorm.Model
EntidadID uint `json:"entidad_id" gorm:"column:entidad_id;index;not null"`
Entidad Entidad `json:"entidad" gorm:"foreignKey:EntidadID"`
ClienteID uint `json:"cliente_id" gorm:"column:cliente_id;index;not null"`
Cliente Cliente `json:"cliente" gorm:"foreignKey:ClienteID"`
EntidadID *uint `json:"entidad_id" gorm:"column:entidad_id;index"`
Entidad *Entidad `json:"entidad" gorm:"foreignKey:EntidadID"`
Fecha time.Time `json:"fecha" gorm:"column:fecha;not null"`
Descripcion string `json:"descripcion" gorm:"column:descripcion;type:text"`
Valor float64 `json:"valor" gorm:"column:valor;not null"`
@@ -260,11 +262,11 @@ func DeleteTransaccion(id uint) error {
func GetAllCuentasCobro(limit, offset int, search string, estado string) ([]CuentaCobro, int64, error) {
var items []CuentaCobro
var total int64
db := app.Http.Database.DB.Model(&CuentaCobro{}).Preload("Entidad").Preload("Transaccion")
db := app.Http.Database.DB.Model(&CuentaCobro{}).Preload("Cliente").Preload("Entidad").Preload("Transaccion")
if search != "" {
db = db.Joins("JOIN contab_entidades ON contab_entidades.id = contab_cuentas_cobro.entidad_id").
Where("contab_entidades.nombre ILIKE ? OR contab_cuentas_cobro.descripcion ILIKE ?",
"%"+search+"%", "%"+search+"%")
db = db.Joins("JOIN clientes ON clientes.id = contab_cuentas_cobro.cliente_id").
Where("clientes.nombre ILIKE ? OR clientes.empresa ILIKE ? OR contab_cuentas_cobro.descripcion ILIKE ?",
"%"+search+"%", "%"+search+"%", "%"+search+"%")
}
if estado != "" {
db = db.Where("contab_cuentas_cobro.estado = ?", estado)
@@ -463,10 +465,7 @@ func CreateCuentaCobro(cc *CuentaCobro) error {
func UpdateCuentaCobro(cc *CuentaCobro) error {
return app.Http.Database.DB.Model(&CuentaCobro{}).Where("id = ?", cc.ID).Updates(map[string]interface{}{
"entidad_id": cc.EntidadID,
"fecha": cc.Fecha,
"descripcion": cc.Descripcion,
"valor": cc.Valor,
"cliente_id": cc.ClienteID,
"estado": cc.Estado,
"fecha_vencimiento": cc.FechaVencimiento,
"fecha_pago": cc.FechaPago,
+11 -11
View File
@@ -26,7 +26,7 @@
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wide">
<tr>
<th class="px-4 py-3 text-left">Fecha</th>
<th class="px-4 py-3 text-left">Entidad</th>
<th class="px-4 py-3 text-left">Cliente</th>
<th class="px-4 py-3 text-left">Descripción</th>
<th class="px-4 py-3 text-right">Valor</th>
<th class="px-4 py-3 text-left">Vence</th>
@@ -41,7 +41,7 @@
<template x-for="c in items" :key="c.ID">
<tr class="hover:bg-slate-50">
<td class="px-4 py-3 text-xs text-slate-500" x-text="formatDate(c.fecha)"></td>
<td class="px-4 py-3 text-slate-700 font-medium" x-text="c.entidad?.nombre||'-'"></td>
<td class="px-4 py-3 text-slate-700 font-medium" x-text="c.cliente?.nombre||'-'"></td>
<td class="px-4 py-3 text-slate-600" x-text="c.descripcion"></td>
<td class="px-4 py-3 text-right font-semibold text-amber-600" x-text="formatoCOP(c.valor)"></td>
<td class="px-4 py-3 text-xs text-slate-500" x-text="formatDate(c.fecha_vencimiento)"></td>
@@ -80,11 +80,11 @@
<form @submit.prevent="save()">
<div class="grid grid-cols-2 gap-4">
<div class="col-span-2">
<label class="label">Entidad (quien debe)</label>
<select x-model.number="form.entidad_id" class="input-field w-full" required>
<label class="label">Cliente (quien debe)</label>
<select x-model.number="form.cliente_id" class="input-field w-full" required>
<option value="">Seleccionar...</option>
<template x-for="e in entidades" :key="e.ID">
<option :value="e.ID" x-text="e.nombre"></option>
<template x-for="cl in clientes" :key="cl.ID">
<option :value="cl.ID" x-text="cl.nombre + (cl.empresa ? ' — ' + cl.empresa : '')"></option>
</template>
</select>
</div>
@@ -149,10 +149,10 @@
<script>
function cobroApp() {
return {
items:[], entidades:[], total:0, totalPages:1, page:1, search:'', filtroEstado:'',
items:[], clientes:[], total:0, totalPages:1, page:1, search:'', filtroEstado:'',
loading:false, saving:false, showModal:false, showDelete:false, showPagarModal:false,
deleteId:null, pagarId:null, pagoFecha:'', error:'',
form:{ entidad_id:'', descripcion:'', valor:0, fecha:'', fecha_vencimiento:'', notas:'' },
form:{ cliente_id:'', descripcion:'', valor:0, fecha:'', fecha_vencimiento:'', notas:'' },
async init(){ await this.load(); },
@@ -163,12 +163,12 @@ function cobroApp() {
if(this.filtroEstado) params.set('estado',this.filtroEstado);
const r=await axios.get('/app/contabilidad/cuentas-cobro/list?'+params.toString());
this.items=r.data.items; this.total=r.data.total; this.totalPages=r.data.totalPages;
const er=await axios.get('/app/contabilidad/entidades/select');
this.entidades=er.data;
const er=await axios.get('/app/api/clientes/select');
this.clientes=er.data;
} finally{ this.loading=false; }
},
openCreate(){ this.editId=null; this.error=''; this.form={entidad_id:'',descripcion:'',valor:0,fecha:'',fecha_vencimiento:'',notas:''}; this.showModal=true; },
openCreate(){ this.editId=null; this.error=''; this.form={cliente_id:'',descripcion:'',valor:0,fecha:'',fecha_vencimiento:'',notas:''}; this.showModal=true; },
async save(){
this.saving=true; this.error='';
try {
+11 -11
View File
@@ -23,7 +23,7 @@
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wide">
<tr>
<th class="px-4 py-3 text-left">Fecha</th>
<th class="px-4 py-3 text-left">Entidad</th>
<th class="px-4 py-3 text-left">Cliente</th>
<th class="px-4 py-3 text-left">Descripción</th>
<th class="px-4 py-3 text-right">Valor</th>
<th class="px-4 py-3 text-left">Vence</th>
@@ -38,7 +38,7 @@
<template x-for="c in items" :key="c.ID">
<tr class="hover:bg-slate-50">
<td class="px-4 py-3 text-xs text-slate-500" x-text="formatDate(c.fecha)"></td>
<td class="px-4 py-3 text-slate-700 font-medium" x-text="c.entidad?.nombre||'-'"></td>
<td class="px-4 py-3 text-slate-700 font-medium" x-text="c.cliente?.nombre||'-'"></td>
<td class="px-4 py-3 text-slate-600" x-text="c.descripcion"></td>
<td class="px-4 py-3 text-right font-semibold text-amber-600" x-text="formatoCOP(c.valor)"></td>
<td class="px-4 py-3 text-xs text-slate-500" x-text="formatDate(c.fecha_vencimiento)"></td>
@@ -73,11 +73,11 @@
<form @submit.prevent="save()">
<div class="grid grid-cols-2 gap-3">
<div class="col-span-2">
<label class="label">Entidad (quien debe)</label>
<select x-model.number="form.entidad_id" class="input-field w-full" required>
<label class="label">Cliente (quien debe)</label>
<select x-model.number="form.cliente_id" class="input-field w-full" required>
<option value="">Seleccionar...</option>
<template x-for="e in entidades" :key="e.ID">
<option :value="e.ID" x-text="e.nombre"></option>
<template x-for="cl in clientes" :key="cl.ID">
<option :value="cl.ID" x-text="cl.nombre + (cl.empresa ? ' — ' + cl.empresa : '')"></option>
</template>
</select>
</div>
@@ -141,10 +141,10 @@
<script>
function cobroApp() {
return {
items:[], entidades:[], total:0, totalPages:1, page:1, search:'', filtroEstado:'',
items:[], clientes:[], total:0, totalPages:1, page:1, search:'', filtroEstado:'',
loading:false, saving:false, showModal:false, showDelete:false, showPagarModal:false,
deleteId:null, pagarId:null, pagoFecha:'', error:'',
form:{ entidad_id:'', descripcion:'', valor:0, fecha:'', fecha_vencimiento:'', notas:'' },
form:{ cliente_id:'', descripcion:'', valor:0, fecha:'', fecha_vencimiento:'', notas:'' },
async init(){ await this.load(); },
@@ -155,12 +155,12 @@ function cobroApp() {
if(this.filtroEstado) params.set('estado',this.filtroEstado);
const r=await axios.get('/app/contabilidad/cuentas-cobro/list?'+params.toString());
this.items=r.data.items; this.total=r.data.total; this.totalPages=r.data.totalPages;
const er=await axios.get('/app/contabilidad/entidades/select');
this.entidades=er.data;
const er=await axios.get('/app/api/clientes/select');
this.clientes=er.data;
} finally{ this.loading=false; }
},
openCreate(){ this.editId=null; this.error=''; this.form={entidad_id:'',descripcion:'',valor:0,fecha:'',fecha_vencimiento:'',notas:''}; this.showModal=true; },
openCreate(){ this.editId=null; this.error=''; this.form={cliente_id:'',descripcion:'',valor:0,fecha:'',fecha_vencimiento:'',notas:''}; this.showModal=true; },
async save(){
this.saving=true; this.error='';
try {
+4 -4
View File
@@ -408,7 +408,7 @@ func GetCuentasCobro(c *fiber.Ctx) error {
func CreateCuentaCobro(c *fiber.Ctx) error {
type Req struct {
EntidadID uint `json:"entidad_id"`
ClienteID uint `json:"cliente_id"`
Fecha string `json:"fecha"`
Descripcion string `json:"descripcion"`
Valor float64 `json:"valor"`
@@ -419,11 +419,11 @@ func CreateCuentaCobro(c *fiber.Ctx) error {
if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
if req.EntidadID == 0 {
return c.Status(400).JSON(fiber.Map{"error": "entidad_id es requerido"})
if req.ClienteID == 0 {
return c.Status(400).JSON(fiber.Map{"error": "cliente_id es requerido"})
}
cc := &models.CuentaCobro{
EntidadID: req.EntidadID,
ClienteID: req.ClienteID,
Descripcion: req.Descripcion,
Valor: req.Valor,
Estado: "pendiente",