fix: compilar agent/dist en builder stage y agregar módulos contabilidad/websms
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ca6a44f64e
commit
e28e2823dc
@@ -0,0 +1,504 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ─── Cuenta (plan de cuentas / categoría contable) ───────────────────────────
|
||||
|
||||
type Cuenta struct {
|
||||
gorm.Model
|
||||
Codigo string `json:"codigo" gorm:"column:codigo;size:20"`
|
||||
Nombre string `json:"nombre" gorm:"column:nombre;size:255;not null"`
|
||||
Tipo string `json:"tipo" gorm:"column:tipo;size:20;not null"` // ingreso | egreso
|
||||
Color string `json:"color" gorm:"column:color;size:7"`
|
||||
Activo bool `json:"activo" gorm:"column:activo;default:true"`
|
||||
}
|
||||
|
||||
func (Cuenta) TableName() string { return "contab_cuentas" }
|
||||
|
||||
// ─── Entidad (cliente, proveedor, empresa, persona) ──────────────────────────
|
||||
|
||||
type Entidad struct {
|
||||
gorm.Model
|
||||
Nombre string `json:"nombre" gorm:"column:nombre;size:255;not null"`
|
||||
Tipo string `json:"tipo" gorm:"column:tipo;size:20"` // cliente | proveedor | ambos
|
||||
Documento string `json:"documento" gorm:"column:documento;size:50"`
|
||||
Email string `json:"email" gorm:"column:email;size:255"`
|
||||
Telefono string `json:"telefono" gorm:"column:telefono;size:50"`
|
||||
Contacto string `json:"contacto" gorm:"column:contacto;size:255"`
|
||||
Notas string `json:"notas" gorm:"column:notas;type:text"`
|
||||
Activo bool `json:"activo" gorm:"column:activo;default:true"`
|
||||
}
|
||||
|
||||
func (Entidad) TableName() string { return "contab_entidades" }
|
||||
|
||||
// ─── Transaccion (ingreso / egreso) ──────────────────────────────────────────
|
||||
|
||||
type Transaccion struct {
|
||||
gorm.Model
|
||||
Fecha time.Time `json:"fecha" gorm:"column:fecha;not null"`
|
||||
Tipo string `json:"tipo" gorm:"column:tipo;size:10;not null"` // ingreso | egreso
|
||||
Descripcion string `json:"descripcion" gorm:"column:descripcion;type:text"`
|
||||
Valor float64 `json:"valor" gorm:"column:valor;not null"`
|
||||
CuentaID *uint `json:"cuenta_id" gorm:"column:cuenta_id;index"`
|
||||
Cuenta *Cuenta `json:"cuenta" gorm:"foreignKey:CuentaID"`
|
||||
EntidadID *uint `json:"entidad_id" gorm:"column:entidad_id;index"`
|
||||
Entidad *Entidad `json:"entidad" gorm:"foreignKey:EntidadID"`
|
||||
FormaPago string `json:"forma_pago" gorm:"column:forma_pago;size:30"` // transferencia | efectivo | tarjeta | cheque | otro
|
||||
Estado string `json:"estado" gorm:"column:estado;size:20;default:'registrada'"`
|
||||
Notas string `json:"notas" gorm:"column:notas;type:text"`
|
||||
}
|
||||
|
||||
func (Transaccion) TableName() string { return "contab_transacciones" }
|
||||
|
||||
// ─── CuentaCobro (cuentas por cobrar) ────────────────────────────────────────
|
||||
|
||||
type CuentaCobro struct {
|
||||
gorm.Model
|
||||
EntidadID uint `json:"entidad_id" gorm:"column:entidad_id;index;not null"`
|
||||
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"`
|
||||
Estado string `json:"estado" gorm:"column:estado;size:20;default:'pendiente'"` // pendiente | pagado | parcial
|
||||
FechaVencimiento *time.Time `json:"fecha_vencimiento" gorm:"column:fecha_vencimiento"`
|
||||
FechaPago *time.Time `json:"fecha_pago" gorm:"column:fecha_pago"`
|
||||
TransaccionID *uint `json:"transaccion_id" gorm:"column:transaccion_id"`
|
||||
Transaccion *Transaccion `json:"transaccion" gorm:"foreignKey:TransaccionID"`
|
||||
Notas string `json:"notas" gorm:"column:notas;type:text"`
|
||||
}
|
||||
|
||||
func (CuentaCobro) TableName() string { return "contab_cuentas_cobro" }
|
||||
|
||||
// ─── CuentaPagar (cuentas por pagar) ─────────────────────────────────────────
|
||||
|
||||
type CuentaPagar struct {
|
||||
gorm.Model
|
||||
EntidadID uint `json:"entidad_id" gorm:"column:entidad_id;index;not null"`
|
||||
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"`
|
||||
Vencimiento *time.Time `json:"vencimiento" gorm:"column:vencimiento"`
|
||||
Estado string `json:"estado" gorm:"column:estado;size:20;default:'pendiente'"` // pendiente | pagado | parcial
|
||||
FechaPago *time.Time `json:"fecha_pago" gorm:"column:fecha_pago"`
|
||||
TransaccionID *uint `json:"transaccion_id" gorm:"column:transaccion_id"`
|
||||
Transaccion *Transaccion `json:"transaccion" gorm:"foreignKey:TransaccionID"`
|
||||
Notas string `json:"notas" gorm:"column:notas;type:text"`
|
||||
}
|
||||
|
||||
func (CuentaPagar) TableName() string { return "contab_cuentas_pagar" }
|
||||
|
||||
// ─── ConsolidadoMensual ──────────────────────────────────────────────────────
|
||||
|
||||
type ConsolidadoMensual struct {
|
||||
gorm.Model
|
||||
Anio int `json:"anio" gorm:"column:anio;not null"`
|
||||
Mes int `json:"mes" gorm:"column:mes;not null"`
|
||||
TotalIngresos float64 `json:"total_ingresos" gorm:"column:total_ingresos;default:0"`
|
||||
TotalEgresos float64 `json:"total_egresos" gorm:"column:total_egresos;default:0"`
|
||||
TotalRetenciones float64 `json:"total_retenciones" gorm:"column:total_retenciones;default:0"`
|
||||
Resultado float64 `json:"resultado" gorm:"column:resultado;default:0"`
|
||||
}
|
||||
|
||||
func (ConsolidadoMensual) TableName() string { return "contab_consolidado" }
|
||||
|
||||
// =============================================================================
|
||||
// ─── CRUD: Cuenta ───────────────────────────────────────────────────────────
|
||||
// =============================================================================
|
||||
|
||||
func GetAllCuentas(limit, offset int, search string) ([]Cuenta, int64, error) {
|
||||
var items []Cuenta
|
||||
var total int64
|
||||
db := app.Http.Database.DB.Model(&Cuenta{})
|
||||
if search != "" {
|
||||
db = db.Where("nombre ILIKE ? OR codigo ILIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := db.Order("tipo ASC, nombre ASC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func GetAllCuentasSelect() ([]Cuenta, error) {
|
||||
var items []Cuenta
|
||||
if err := app.Http.Database.DB.Where("activo = ?", true).Order("nombre ASC").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func CreateCuenta(c *Cuenta) error {
|
||||
return app.Http.Database.DB.Create(c).Error
|
||||
}
|
||||
|
||||
func UpdateCuenta(c *Cuenta) error {
|
||||
return app.Http.Database.DB.Model(&Cuenta{}).Where("id = ?", c.ID).Updates(map[string]interface{}{
|
||||
"codigo": c.Codigo,
|
||||
"nombre": c.Nombre,
|
||||
"tipo": c.Tipo,
|
||||
"color": c.Color,
|
||||
"activo": c.Activo,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func DeleteCuenta(id uint) error {
|
||||
return app.Http.Database.DB.Delete(&Cuenta{}, id).Error
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ─── CRUD: Entidad ──────────────────────────────────────────────────────────
|
||||
// =============================================================================
|
||||
|
||||
func GetAllEntidades(limit, offset int, search string) ([]Entidad, int64, error) {
|
||||
var items []Entidad
|
||||
var total int64
|
||||
db := app.Http.Database.DB.Model(&Entidad{})
|
||||
if search != "" {
|
||||
db = db.Where("nombre ILIKE ? OR documento ILIKE ? OR email ILIKE ?",
|
||||
"%"+search+"%", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := db.Order("nombre ASC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func GetAllEntidadesSelect() ([]Entidad, error) {
|
||||
var items []Entidad
|
||||
if err := app.Http.Database.DB.Where("activo = ?", true).Order("nombre ASC").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func CreateEntidad(e *Entidad) error {
|
||||
return app.Http.Database.DB.Create(e).Error
|
||||
}
|
||||
|
||||
func UpdateEntidad(e *Entidad) error {
|
||||
return app.Http.Database.DB.Model(&Entidad{}).Where("id = ?", e.ID).Updates(map[string]interface{}{
|
||||
"nombre": e.Nombre,
|
||||
"tipo": e.Tipo,
|
||||
"documento": e.Documento,
|
||||
"email": e.Email,
|
||||
"telefono": e.Telefono,
|
||||
"contacto": e.Contacto,
|
||||
"notas": e.Notas,
|
||||
"activo": e.Activo,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func DeleteEntidad(id uint) error {
|
||||
return app.Http.Database.DB.Delete(&Entidad{}, id).Error
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ─── CRUD: Transaccion ──────────────────────────────────────────────────────
|
||||
// =============================================================================
|
||||
|
||||
func GetAllTransacciones(limit, offset int, search string, filtroTipo string, mes int, anio int) ([]Transaccion, int64, error) {
|
||||
var items []Transaccion
|
||||
var total int64
|
||||
db := app.Http.Database.DB.Model(&Transaccion{}).Preload("Cuenta").Preload("Entidad")
|
||||
if search != "" {
|
||||
db = db.Where("descripcion ILIKE ?", "%"+search+"%")
|
||||
}
|
||||
if filtroTipo != "" {
|
||||
db = db.Where("tipo = ?", filtroTipo)
|
||||
}
|
||||
if mes > 0 && anio > 0 {
|
||||
db = db.Where("EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", mes, anio)
|
||||
}
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := db.Order("fecha DESC, created_at DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func CreateTransaccion(t *Transaccion) error {
|
||||
return app.Http.Database.DB.Create(t).Error
|
||||
}
|
||||
|
||||
func UpdateTransaccion(t *Transaccion) error {
|
||||
return app.Http.Database.DB.Model(&Transaccion{}).Where("id = ?", t.ID).Updates(map[string]interface{}{
|
||||
"fecha": t.Fecha,
|
||||
"tipo": t.Tipo,
|
||||
"descripcion": t.Descripcion,
|
||||
"valor": t.Valor,
|
||||
"cuenta_id": t.CuentaID,
|
||||
"entidad_id": t.EntidadID,
|
||||
"forma_pago": t.FormaPago,
|
||||
"estado": t.Estado,
|
||||
"notas": t.Notas,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func DeleteTransaccion(id uint) error {
|
||||
return app.Http.Database.DB.Delete(&Transaccion{}, id).Error
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ─── CRUD: CuentaCobro ──────────────────────────────────────────────────────
|
||||
// =============================================================================
|
||||
|
||||
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")
|
||||
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+"%")
|
||||
}
|
||||
if estado != "" {
|
||||
db = db.Where("contab_cuentas_cobro.estado = ?", estado)
|
||||
}
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := db.Order("contab_cuentas_cobro.fecha DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func CreateCuentaCobro(cc *CuentaCobro) error {
|
||||
return app.Http.Database.DB.Create(cc).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,
|
||||
"estado": cc.Estado,
|
||||
"fecha_vencimiento": cc.FechaVencimiento,
|
||||
"fecha_pago": cc.FechaPago,
|
||||
"transaccion_id": cc.TransaccionID,
|
||||
"notas": cc.Notas,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func DeleteCuentaCobro(id uint) error {
|
||||
return app.Http.Database.DB.Delete(&CuentaCobro{}, id).Error
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ─── CRUD: CuentaPagar ──────────────────────────────────────────────────────
|
||||
// =============================================================================
|
||||
|
||||
func GetAllCuentasPagar(limit, offset int, search string, estado string) ([]CuentaPagar, int64, error) {
|
||||
var items []CuentaPagar
|
||||
var total int64
|
||||
db := app.Http.Database.DB.Model(&CuentaPagar{}).Preload("Entidad").Preload("Transaccion")
|
||||
if search != "" {
|
||||
db = db.Joins("JOIN contab_entidades ON contab_entidades.id = contab_cuentas_pagar.entidad_id").
|
||||
Where("contab_entidades.nombre ILIKE ? OR contab_cuentas_pagar.descripcion ILIKE ?",
|
||||
"%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
if estado != "" {
|
||||
db = db.Where("contab_cuentas_pagar.estado = ?", estado)
|
||||
}
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := db.Order("contab_cuentas_pagar.fecha DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func CreateCuentaPagar(cp *CuentaPagar) error {
|
||||
return app.Http.Database.DB.Create(cp).Error
|
||||
}
|
||||
|
||||
func UpdateCuentaPagar(cp *CuentaPagar) error {
|
||||
return app.Http.Database.DB.Model(&CuentaPagar{}).Where("id = ?", cp.ID).Updates(map[string]interface{}{
|
||||
"entidad_id": cp.EntidadID,
|
||||
"fecha": cp.Fecha,
|
||||
"descripcion": cp.Descripcion,
|
||||
"valor": cp.Valor,
|
||||
"vencimiento": cp.Vencimiento,
|
||||
"estado": cp.Estado,
|
||||
"fecha_pago": cp.FechaPago,
|
||||
"transaccion_id": cp.TransaccionID,
|
||||
"notas": cp.Notas,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func DeleteCuentaPagar(id uint) error {
|
||||
return app.Http.Database.DB.Delete(&CuentaPagar{}, id).Error
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ─── Consolidado ────────────────────────────────────────────────────────────
|
||||
// =============================================================================
|
||||
|
||||
func GetConsolidadoMensual(mes, anio int) (*ConsolidadoMensual, error) {
|
||||
var c ConsolidadoMensual
|
||||
err := app.Http.Database.DB.Where("mes = ? AND anio = ?", mes, anio).First(&c).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func CalcularYGuardarConsolidado(mes, anio int) (*ConsolidadoMensual, error) {
|
||||
type SumRow struct {
|
||||
Total float64
|
||||
}
|
||||
var ingresos SumRow
|
||||
var egresos SumRow
|
||||
|
||||
app.Http.Database.DB.Model(&Transaccion{}).
|
||||
Select("COALESCE(SUM(valor),0) as total").
|
||||
Where("tipo = ? AND EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", "ingreso", mes, anio).
|
||||
Scan(&ingresos)
|
||||
|
||||
app.Http.Database.DB.Model(&Transaccion{}).
|
||||
Select("COALESCE(SUM(valor),0) as total").
|
||||
Where("tipo = ? AND EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", "egreso", mes, anio).
|
||||
Scan(&egresos)
|
||||
|
||||
resultado := ingresos.Total - egresos.Total
|
||||
|
||||
c := &ConsolidadoMensual{
|
||||
Anio: anio,
|
||||
Mes: mes,
|
||||
TotalIngresos: ingresos.Total,
|
||||
TotalEgresos: egresos.Total,
|
||||
TotalRetenciones: 0,
|
||||
Resultado: resultado,
|
||||
}
|
||||
|
||||
var existing ConsolidadoMensual
|
||||
err := app.Http.Database.DB.Where("mes = ? AND anio = ?", mes, anio).First(&existing).Error
|
||||
if err == nil {
|
||||
c.ID = existing.ID
|
||||
app.Http.Database.DB.Model(&ConsolidadoMensual{}).Where("id = ?", c.ID).Updates(map[string]interface{}{
|
||||
"total_ingresos": c.TotalIngresos,
|
||||
"total_egresos": c.TotalEgresos,
|
||||
"total_retenciones": c.TotalRetenciones,
|
||||
"resultado": c.Resultado,
|
||||
})
|
||||
} else {
|
||||
app.Http.Database.DB.Create(c)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func ListConsolidados(anio int) ([]ConsolidadoMensual, error) {
|
||||
var items []ConsolidadoMensual
|
||||
db := app.Http.Database.DB.Model(&ConsolidadoMensual{}).Order("anio DESC, mes DESC")
|
||||
if anio > 0 {
|
||||
db = db.Where("anio = ?", anio)
|
||||
}
|
||||
if err := db.Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// ─── Datos para dashboard ────────────────────────────────────────────────────
|
||||
|
||||
type DashboardData struct {
|
||||
Mes int `json:"mes"`
|
||||
Anio int `json:"anio"`
|
||||
TotalIngresos float64 `json:"total_ingresos"`
|
||||
TotalEgresos float64 `json:"total_egresos"`
|
||||
Resultado float64 `json:"resultado"`
|
||||
CantTransacciones int64 `json:"cant_transacciones"`
|
||||
PendientesCobro float64 `json:"pendientes_cobro"`
|
||||
PendientesPago float64 `json:"pendientes_pago"`
|
||||
Transacciones []Transaccion `json:"transacciones"`
|
||||
}
|
||||
|
||||
func GetDashboardData(mes, anio int) (*DashboardData, error) {
|
||||
d := &DashboardData{Mes: mes, Anio: anio}
|
||||
|
||||
app.Http.Database.DB.Model(&Transaccion{}).
|
||||
Select("COALESCE(SUM(valor),0) as total").
|
||||
Where("tipo = ? AND EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", "ingreso", mes, anio).
|
||||
Scan(&d.TotalIngresos)
|
||||
|
||||
app.Http.Database.DB.Model(&Transaccion{}).
|
||||
Select("COALESCE(SUM(valor),0) as total").
|
||||
Where("tipo = ? AND EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", "egreso", mes, anio).
|
||||
Scan(&d.TotalEgresos)
|
||||
|
||||
d.Resultado = d.TotalIngresos - d.TotalEgresos
|
||||
|
||||
app.Http.Database.DB.Model(&Transaccion{}).
|
||||
Where("EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", mes, anio).
|
||||
Count(&d.CantTransacciones)
|
||||
|
||||
app.Http.Database.DB.Model(&CuentaCobro{}).
|
||||
Select("COALESCE(SUM(valor),0) as total").
|
||||
Where("estado = ?", "pendiente").
|
||||
Scan(&d.PendientesCobro)
|
||||
|
||||
app.Http.Database.DB.Model(&CuentaPagar{}).
|
||||
Select("COALESCE(SUM(valor),0) as total").
|
||||
Where("estado = ?", "pendiente").
|
||||
Scan(&d.PendientesPago)
|
||||
|
||||
app.Http.Database.DB.Model(&Transaccion{}).
|
||||
Preload("Cuenta").Preload("Entidad").
|
||||
Where("EXTRACT(MONTH FROM fecha) = ? AND EXTRACT(YEAR FROM fecha) = ?", mes, anio).
|
||||
Order("fecha DESC").
|
||||
Limit(10).
|
||||
Find(&d.Transacciones)
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func SeedContabilidad() {
|
||||
cuentas := []Cuenta{
|
||||
{Codigo: "ING-FAC", Nombre: "Facturación", Tipo: "ingreso", Color: "#22c55e"},
|
||||
{Codigo: "ING-OTR", Nombre: "Otros ingresos", Tipo: "ingreso", Color: "#16a34a"},
|
||||
{Codigo: "EGR-HOS", Nombre: "Hosting/Servidores", Tipo: "egreso", Color: "#ef4444"},
|
||||
{Codigo: "EGR-DOM", Nombre: "Dominios", Tipo: "egreso", Color: "#dc2626"},
|
||||
{Codigo: "EGR-SRV", Nombre: "Servicios", Tipo: "egreso", Color: "#f97316"},
|
||||
{Codigo: "EGR-GRAL", Nombre: "Gastos generales", Tipo: "egreso", Color: "#eab308"},
|
||||
{Codigo: "EGR-NOM", Nombre: "Nómina", Tipo: "egreso", Color: "#a855f7"},
|
||||
{Codigo: "EGR-IMPU", Nombre: "Impuestos", Tipo: "egreso", Color: "#6366f1"},
|
||||
{Codigo: "EGR-MKT", Nombre: "Marketing", Tipo: "egreso", Color: "#ec4899"},
|
||||
}
|
||||
for _, c := range cuentas {
|
||||
var existing Cuenta
|
||||
if app.Http.Database.DB.Where("codigo = ?", c.Codigo).First(&existing).Error != nil {
|
||||
app.Http.Database.DB.Create(&c)
|
||||
}
|
||||
}
|
||||
|
||||
entidades := []Entidad{
|
||||
{Nombre: "ANDREMER", Tipo: "cliente"},
|
||||
{Nombre: "GIAF SAS", Tipo: "cliente"},
|
||||
{Nombre: "TECZONE", Tipo: "cliente"},
|
||||
{Nombre: "DOCUXER", Tipo: "cliente"},
|
||||
{Nombre: "FELIPE", Tipo: "cliente"},
|
||||
{Nombre: "NATALIA", Tipo: "cliente"},
|
||||
}
|
||||
for _, e := range entidades {
|
||||
var existing Entidad
|
||||
if app.Http.Database.DB.Where("nombre = ?", e.Nombre).First(&existing).Error != nil {
|
||||
app.Http.Database.DB.Create(&e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WebSmsConfig struct {
|
||||
gorm.Model
|
||||
Username string `json:"username" gorm:"column:username;size:255;not null"`
|
||||
ApiToken string `json:"api_token" gorm:"column:api_token;type:text;not null"`
|
||||
Sender string `json:"sender" gorm:"column:sender;size:30"`
|
||||
Notas string `json:"notas" gorm:"column:notas;type:text"`
|
||||
Activo bool `json:"activo" gorm:"column:activo;default:true"`
|
||||
}
|
||||
|
||||
func (WebSmsConfig) TableName() string { return "websms_config" }
|
||||
|
||||
func GetWebSmsConfig() (*WebSmsConfig, error) {
|
||||
var item WebSmsConfig
|
||||
if err := app.Http.Database.DB.Where("activo = ?", true).Order("id DESC").First(&item).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func SaveWebSmsConfig(s WebSmsConfig) error {
|
||||
app.Http.Database.DB.Model(&WebSmsConfig{}).
|
||||
Where("activo = ?", true).
|
||||
Update("activo", false)
|
||||
s.Activo = true
|
||||
if s.ID > 0 {
|
||||
return app.Http.Database.DB.Model(&s).Updates(map[string]interface{}{
|
||||
"username": s.Username,
|
||||
"api_token": s.ApiToken,
|
||||
"sender": s.Sender,
|
||||
"notas": s.Notas,
|
||||
"activo": true,
|
||||
}).Error
|
||||
}
|
||||
return app.Http.Database.DB.Create(&s).Error
|
||||
}
|
||||
|
||||
// ─── Log de SMS enviados ────────────────────────────────────────────────────
|
||||
|
||||
type WebSmsLog struct {
|
||||
gorm.Model
|
||||
Para string `json:"para" gorm:"column:para;size:30;index"`
|
||||
Mensaje string `json:"mensaje" gorm:"column:mensaje;type:text"`
|
||||
Status string `json:"status" gorm:"column:status;size:20"`
|
||||
MsgID string `json:"msg_id" gorm:"column:msg_id;size:100"`
|
||||
Error string `json:"error" gorm:"column:error;type:text"`
|
||||
}
|
||||
|
||||
func (WebSmsLog) TableName() string { return "websms_log" }
|
||||
|
||||
func CreateWebSmsLog(entry *WebSmsLog) error {
|
||||
return app.Http.Database.DB.Create(entry).Error
|
||||
}
|
||||
|
||||
func GetWebSmsLogs(limit int) ([]WebSmsLog, error) {
|
||||
var logs []WebSmsLog
|
||||
if err := app.Http.Database.DB.Order("id DESC").Limit(limit).Find(&logs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
// ─── Webhook ACK log ────────────────────────────────────────────────────────
|
||||
|
||||
type WebSmsWebhookLog struct {
|
||||
gorm.Model
|
||||
Tipo string `json:"tipo" gorm:"column:tipo;size:20;index"` // delivery | click | incoming
|
||||
MsgID string `json:"msg_id" gorm:"column:msg_id;size:100;index"`
|
||||
Para string `json:"para" gorm:"column:para;size:30"`
|
||||
Status string `json:"status" gorm:"column:status;size:30"`
|
||||
Raw string `json:"raw" gorm:"column:raw;type:text"`
|
||||
}
|
||||
|
||||
func (WebSmsWebhookLog) TableName() string { return "websms_webhook_log" }
|
||||
|
||||
func CreateWebSmsWebhookLog(entry *WebSmsWebhookLog) error {
|
||||
return app.Http.Database.DB.Create(entry).Error
|
||||
}
|
||||
|
||||
func GetWebSmsWebhookLogs(limit int) ([]WebSmsWebhookLog, error) {
|
||||
var logs []WebSmsWebhookLog
|
||||
if err := app.Http.Database.DB.Order("id DESC").Limit(limit).Find(&logs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return logs, nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
||||
)
|
||||
|
||||
const websmsAPIBase = "https://websms.labsmobile.com/SY0204/api"
|
||||
|
||||
type WebSmsRequest struct {
|
||||
Message string `json:"message"`
|
||||
TPOA string `json:"tpoa,omitempty"`
|
||||
Recipient []WebSmsRecipient `json:"recipient"`
|
||||
}
|
||||
|
||||
type WebSmsRecipient struct {
|
||||
MSISDN string `json:"msisdn"`
|
||||
}
|
||||
|
||||
type WebSmsResponse struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type WebSmsAckPayload struct {
|
||||
ID string `json:"id"`
|
||||
Reference string `json:"reference"`
|
||||
Status string `json:"status"`
|
||||
Msisdn string `json:"msisdn"`
|
||||
Substatus string `json:"substatus"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
type WebSmsClickPayload struct {
|
||||
ID string `json:"id"`
|
||||
Reference string `json:"reference"`
|
||||
Msisdn string `json:"msisdn"`
|
||||
URL string `json:"url"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
type WebSmsIncomingPayload struct {
|
||||
ID string `json:"id"`
|
||||
Msisdn string `json:"msisdn"`
|
||||
Message string `json:"message"`
|
||||
Shortcode string `json:"shortcode"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
func SendWebSms(cfg *models.WebSmsConfig, para, mensaje string) (*WebSmsResponse, error) {
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(cfg.Username + ":" + cfg.ApiToken))
|
||||
|
||||
req := WebSmsRequest{
|
||||
Message: mensaje,
|
||||
Recipient: []WebSmsRecipient{{MSISDN: para}},
|
||||
}
|
||||
if cfg.Sender != "" {
|
||||
req.TPOA = cfg.Sender
|
||||
}
|
||||
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("websms: marshal: %w", err)
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest("POST", websmsAPIBase, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "Basic "+auth)
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("websms: http: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
|
||||
return nil, fmt.Errorf("websms API status %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var result WebSmsResponse
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, fmt.Errorf("websms: unmarshal: %w", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user