- El agente creaba tareas con estado='pendiente' (default), pero el tablero Kanban del dashboard solo reconoce por_hacer/en_progreso/revision/hecho. La tarea se guardaba bien (por eso llegaba el correo de notificación) pero no aparecía en ninguna columna. Se corrigen los enums y el default de las tools crear_tarea/actualizar_estado_tarea, y se agrega una reparación única al arranque que corrige las tareas ya creadas con el estado inválido. - adjuntar_factura siempre asumía factura de VENTA (ligada a un cliente). Se agrega adjuntar_factura_compra: cuando un proveedor le factura a U-SITE (no al revés), busca/crea la Entidad proveedor y registra una cuenta por pagar con el documento adjunto (se agregan campos archivo/original_name/tipo_mime a CuentaPagar, que no los tenía). El prompt del sistema instruye a Claude a decidir la dirección leyendo quién emite y quién recibe el documento. De paso: endpoint de descarga del soporte de la factura de compra, expuesto también en el dashboard (/app/contabilidad/cuentas-pagar). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
694 lines
21 KiB
Go
694 lines
21 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
|
|
)
|
|
|
|
// ─── Render de vistas ────────────────────────────────────────────────────────
|
|
|
|
func ContabilidadIndex(c *fiber.Ctx) error {
|
|
return c.Render("contabilidad/contabilidad", fiber.Map{
|
|
"user": c.Locals("user"),
|
|
"modules": c.Locals("userModules"),
|
|
}, "layouts/main")
|
|
}
|
|
|
|
func ContabilidadTransaccionesView(c *fiber.Ctx) error {
|
|
return c.Render("contabilidad/transacciones", fiber.Map{
|
|
"user": c.Locals("user"),
|
|
"modules": c.Locals("userModules"),
|
|
}, "layouts/main")
|
|
}
|
|
|
|
func ContabilidadCuentasView(c *fiber.Ctx) error {
|
|
return c.Render("contabilidad/cuentas", fiber.Map{
|
|
"user": c.Locals("user"),
|
|
"modules": c.Locals("userModules"),
|
|
}, "layouts/main")
|
|
}
|
|
|
|
func ContabilidadEntidadesView(c *fiber.Ctx) error {
|
|
return c.Render("contabilidad/entidades", fiber.Map{
|
|
"user": c.Locals("user"),
|
|
"modules": c.Locals("userModules"),
|
|
}, "layouts/main")
|
|
}
|
|
|
|
func ContabilidadCobroView(c *fiber.Ctx) error {
|
|
return c.Render("contabilidad/cuentas_cobro", fiber.Map{
|
|
"user": c.Locals("user"),
|
|
"modules": c.Locals("userModules"),
|
|
}, "layouts/main")
|
|
}
|
|
|
|
func ContabilidadPagarView(c *fiber.Ctx) error {
|
|
return c.Render("contabilidad/cuentas_pagar", fiber.Map{
|
|
"user": c.Locals("user"),
|
|
"modules": c.Locals("userModules"),
|
|
}, "layouts/main")
|
|
}
|
|
|
|
// ─── Dashboard ───────────────────────────────────────────────────────────────
|
|
|
|
func ContabilidadDashboard(c *fiber.Ctx) error {
|
|
now := time.Now()
|
|
mes, _ := strconv.Atoi(c.Query("mes", strconv.Itoa(int(now.Month()))))
|
|
anio, _ := strconv.Atoi(c.Query("anio", strconv.Itoa(now.Year())))
|
|
if mes < 1 || mes > 12 {
|
|
mes = int(now.Month())
|
|
}
|
|
if anio < 2000 {
|
|
anio = now.Year()
|
|
}
|
|
data, err := models.GetDashboardData(mes, anio)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(data)
|
|
}
|
|
|
|
func ContabilidadConsolidado(ctx *fiber.Ctx) error {
|
|
now := time.Now()
|
|
mes, _ := strconv.Atoi(ctx.Query("mes", strconv.Itoa(int(now.Month()))))
|
|
anio, _ := strconv.Atoi(ctx.Query("anio", strconv.Itoa(now.Year())))
|
|
if mes < 1 || mes > 12 {
|
|
mes = int(now.Month())
|
|
}
|
|
if anio < 2000 {
|
|
anio = now.Year()
|
|
}
|
|
data, err := models.CalcularYGuardarConsolidado(mes, anio)
|
|
if err != nil {
|
|
return ctx.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return ctx.JSON(data)
|
|
}
|
|
|
|
func ContabilidadListConsolidados(c *fiber.Ctx) error {
|
|
anio, _ := strconv.Atoi(c.Query("anio", "0"))
|
|
items, err := models.ListConsolidados(anio)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(items)
|
|
}
|
|
|
|
// =============================================================================
|
|
// ─── CRUD: Cuentas ──────────────────────────────────────────────────────────
|
|
// =============================================================================
|
|
|
|
func GetCuentas(c *fiber.Ctx) error {
|
|
page, _ := strconv.Atoi(c.Query("page", "1"))
|
|
search := c.Query("search", "")
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
limit := 50
|
|
offset := (page - 1) * limit
|
|
items, total, err := models.GetAllCuentas(limit, offset, search)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"items": items,
|
|
"total": total,
|
|
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
|
|
"page": page,
|
|
})
|
|
}
|
|
|
|
func GetCuentasSelect(c *fiber.Ctx) error {
|
|
items, err := models.GetAllCuentasSelect()
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(items)
|
|
}
|
|
|
|
func CreateCuenta(c *fiber.Ctx) error {
|
|
var req models.Cuenta
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
if req.Nombre == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "nombre es requerido"})
|
|
}
|
|
if req.Tipo == "" {
|
|
req.Tipo = "egreso"
|
|
}
|
|
req.Activo = true
|
|
if err := models.CreateCuenta(&req); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.Status(201).JSON(req)
|
|
}
|
|
|
|
func UpdateCuenta(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
var req models.Cuenta
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
req.ID = uint(id)
|
|
if err := models.UpdateCuenta(&req); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
func DeleteCuenta(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
if err := models.DeleteCuenta(uint(id)); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// =============================================================================
|
|
// ─── CRUD: Entidades ────────────────────────────────────────────────────────
|
|
// =============================================================================
|
|
|
|
func GetEntidades(c *fiber.Ctx) error {
|
|
page, _ := strconv.Atoi(c.Query("page", "1"))
|
|
search := c.Query("search", "")
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
limit := 20
|
|
offset := (page - 1) * limit
|
|
items, total, err := models.GetAllEntidades(limit, offset, search)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"items": items,
|
|
"total": total,
|
|
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
|
|
"page": page,
|
|
})
|
|
}
|
|
|
|
func GetEntidadesSelect(c *fiber.Ctx) error {
|
|
items, err := models.GetAllEntidadesSelect()
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(items)
|
|
}
|
|
|
|
func CreateEntidad(c *fiber.Ctx) error {
|
|
var req models.Entidad
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
if req.Nombre == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "nombre es requerido"})
|
|
}
|
|
req.Activo = true
|
|
if err := models.CreateEntidad(&req); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.Status(201).JSON(req)
|
|
}
|
|
|
|
func UpdateEntidad(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
var req models.Entidad
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
req.ID = uint(id)
|
|
if err := models.UpdateEntidad(&req); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
func DeleteEntidad(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
if err := models.DeleteEntidad(uint(id)); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// =============================================================================
|
|
// ─── CRUD: Transacciones ────────────────────────────────────────────────────
|
|
// =============================================================================
|
|
|
|
func GetTransacciones(c *fiber.Ctx) error {
|
|
page, _ := strconv.Atoi(c.Query("page", "1"))
|
|
search := c.Query("search", "")
|
|
filtroTipo := c.Query("tipo", "")
|
|
mes, _ := strconv.Atoi(c.Query("mes", "0"))
|
|
anio, _ := strconv.Atoi(c.Query("anio", "0"))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
limit := 30
|
|
offset := (page - 1) * limit
|
|
items, total, err := models.GetAllTransacciones(limit, offset, search, filtroTipo, mes, anio)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"items": items,
|
|
"total": total,
|
|
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
|
|
"page": page,
|
|
})
|
|
}
|
|
|
|
func CreateTransaccion(c *fiber.Ctx) error {
|
|
type Req struct {
|
|
Fecha string `json:"fecha"`
|
|
Tipo string `json:"tipo"`
|
|
Descripcion string `json:"descripcion"`
|
|
Valor float64 `json:"valor"`
|
|
CuentaID *uint `json:"cuenta_id"`
|
|
EntidadID *uint `json:"entidad_id"`
|
|
FormaPago string `json:"forma_pago"`
|
|
Estado string `json:"estado"`
|
|
Notas string `json:"notas"`
|
|
}
|
|
var req Req
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
if req.Descripcion == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "descripcion es requerida"})
|
|
}
|
|
if req.Valor <= 0 {
|
|
return c.Status(400).JSON(fiber.Map{"error": "valor debe ser mayor a 0"})
|
|
}
|
|
t := &models.Transaccion{
|
|
Tipo: req.Tipo,
|
|
Descripcion: req.Descripcion,
|
|
Valor: req.Valor,
|
|
CuentaID: req.CuentaID,
|
|
EntidadID: req.EntidadID,
|
|
FormaPago: req.FormaPago,
|
|
Estado: req.Estado,
|
|
Notas: req.Notas,
|
|
Fecha: time.Now(),
|
|
}
|
|
if t.Tipo == "" {
|
|
t.Tipo = "ingreso"
|
|
}
|
|
if t.Estado == "" {
|
|
t.Estado = "registrada"
|
|
}
|
|
if req.Fecha != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.Fecha); err == nil {
|
|
t.Fecha = parsed
|
|
}
|
|
}
|
|
if err := models.CreateTransaccion(t); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.Status(201).JSON(t)
|
|
}
|
|
|
|
func UpdateTransaccion(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
type Req struct {
|
|
Fecha string `json:"fecha"`
|
|
Tipo string `json:"tipo"`
|
|
Descripcion string `json:"descripcion"`
|
|
Valor float64 `json:"valor"`
|
|
CuentaID *uint `json:"cuenta_id"`
|
|
EntidadID *uint `json:"entidad_id"`
|
|
FormaPago string `json:"forma_pago"`
|
|
Estado string `json:"estado"`
|
|
Notas string `json:"notas"`
|
|
}
|
|
var req Req
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
t := &models.Transaccion{
|
|
Tipo: req.Tipo,
|
|
Descripcion: req.Descripcion,
|
|
Valor: req.Valor,
|
|
CuentaID: req.CuentaID,
|
|
EntidadID: req.EntidadID,
|
|
FormaPago: req.FormaPago,
|
|
Estado: req.Estado,
|
|
Notas: req.Notas,
|
|
Fecha: time.Now(),
|
|
}
|
|
t.ID = uint(id)
|
|
if req.Fecha != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.Fecha); err == nil {
|
|
t.Fecha = parsed
|
|
}
|
|
}
|
|
|
|
if err := models.UpdateTransaccion(t); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
func DeleteTransaccion(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
if err := models.DeleteTransaccion(uint(id)); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// =============================================================================
|
|
// ─── CRUD: Cuentas por Cobrar ──────────────────────────────────────────────
|
|
// =============================================================================
|
|
|
|
func GetCuentasCobro(c *fiber.Ctx) error {
|
|
page, _ := strconv.Atoi(c.Query("page", "1"))
|
|
search := c.Query("search", "")
|
|
estado := c.Query("estado", "")
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
limit := 20
|
|
offset := (page - 1) * limit
|
|
items, total, err := models.GetAllCuentasCobro(limit, offset, search, estado)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"items": items,
|
|
"total": total,
|
|
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
|
|
"page": page,
|
|
})
|
|
}
|
|
|
|
func CreateCuentaCobro(c *fiber.Ctx) error {
|
|
type Req struct {
|
|
ClienteID uint `json:"cliente_id"`
|
|
Fecha string `json:"fecha"`
|
|
Descripcion string `json:"descripcion"`
|
|
Valor float64 `json:"valor"`
|
|
FechaVencimiento string `json:"fecha_vencimiento"`
|
|
Notas string `json:"notas"`
|
|
}
|
|
var req Req
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
if req.ClienteID == 0 {
|
|
return c.Status(400).JSON(fiber.Map{"error": "cliente_id es requerido"})
|
|
}
|
|
cc := &models.CuentaCobro{
|
|
ClienteID: req.ClienteID,
|
|
Descripcion: req.Descripcion,
|
|
Valor: req.Valor,
|
|
Estado: "pendiente",
|
|
Notas: req.Notas,
|
|
Fecha: time.Now(),
|
|
}
|
|
if req.Fecha != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.Fecha); err == nil {
|
|
cc.Fecha = parsed
|
|
}
|
|
}
|
|
if req.FechaVencimiento != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.FechaVencimiento); err == nil {
|
|
cc.FechaVencimiento = &parsed
|
|
}
|
|
}
|
|
if err := models.CreateCuentaCobro(cc); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.Status(201).JSON(cc)
|
|
}
|
|
|
|
func UpdateCuentaCobro(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
type Req struct {
|
|
Estado string `json:"estado"`
|
|
FechaPago string `json:"fecha_pago"`
|
|
TransaccionID *uint `json:"transaccion_id"`
|
|
Notas string `json:"notas"`
|
|
FechaVencimiento string `json:"fecha_vencimiento"`
|
|
Descripcion string `json:"descripcion"`
|
|
Valor float64 `json:"valor"`
|
|
}
|
|
var req Req
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
cc := &models.CuentaCobro{}
|
|
cc.ID = uint(id)
|
|
if req.Estado != "" {
|
|
cc.Estado = req.Estado
|
|
}
|
|
if req.FechaPago != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.FechaPago); err == nil {
|
|
cc.FechaPago = &parsed
|
|
}
|
|
}
|
|
cc.TransaccionID = req.TransaccionID
|
|
cc.Notas = req.Notas
|
|
if err := models.UpdateCuentaCobro(cc); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
func DeleteCuentaCobro(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
if err := models.DeleteCuentaCobro(uint(id)); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// MarcarCuentaCobroPagada registra el pago y crea la transacción de ingreso vinculada.
|
|
// POST /contabilidad/cuentas-cobro/:id/pagar
|
|
func MarcarCuentaCobroPagada(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
var req struct {
|
|
FechaPago string `json:"fecha_pago"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
fecha := time.Now()
|
|
if req.FechaPago != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.FechaPago); err == nil {
|
|
fecha = parsed
|
|
}
|
|
}
|
|
if err := models.MarcarCuentaCobroPagada(uint(id), fecha); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// =============================================================================
|
|
// ─── CRUD: Cuentas por Pagar ────────────────────────────────────────────────
|
|
// =============================================================================
|
|
|
|
func GetCuentasPagar(c *fiber.Ctx) error {
|
|
page, _ := strconv.Atoi(c.Query("page", "1"))
|
|
search := c.Query("search", "")
|
|
estado := c.Query("estado", "")
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
limit := 20
|
|
offset := (page - 1) * limit
|
|
items, total, err := models.GetAllCuentasPagar(limit, offset, search, estado)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"items": items,
|
|
"total": total,
|
|
"totalPages": int(math.Ceil(float64(total) / float64(limit))),
|
|
"page": page,
|
|
})
|
|
}
|
|
|
|
func CreateCuentaPagar(c *fiber.Ctx) error {
|
|
type Req struct {
|
|
EntidadID uint `json:"entidad_id"`
|
|
Fecha string `json:"fecha"`
|
|
Descripcion string `json:"descripcion"`
|
|
Valor float64 `json:"valor"`
|
|
Vencimiento string `json:"vencimiento"`
|
|
Notas string `json:"notas"`
|
|
}
|
|
var req Req
|
|
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"})
|
|
}
|
|
cp := &models.CuentaPagar{
|
|
EntidadID: req.EntidadID,
|
|
Descripcion: req.Descripcion,
|
|
Valor: req.Valor,
|
|
Estado: "pendiente",
|
|
Notas: req.Notas,
|
|
Fecha: time.Now(),
|
|
}
|
|
if req.Fecha != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.Fecha); err == nil {
|
|
cp.Fecha = parsed
|
|
}
|
|
}
|
|
if req.Vencimiento != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.Vencimiento); err == nil {
|
|
cp.Vencimiento = &parsed
|
|
}
|
|
}
|
|
if err := models.CreateCuentaPagar(cp); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.Status(201).JSON(cp)
|
|
}
|
|
|
|
func UpdateCuentaPagar(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
type Req struct {
|
|
Estado string `json:"estado"`
|
|
FechaPago string `json:"fecha_pago"`
|
|
TransaccionID *uint `json:"transaccion_id"`
|
|
Notas string `json:"notas"`
|
|
}
|
|
var req Req
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
cp := &models.CuentaPagar{}
|
|
cp.ID = uint(id)
|
|
if req.Estado != "" {
|
|
cp.Estado = req.Estado
|
|
}
|
|
if req.FechaPago != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.FechaPago); err == nil {
|
|
cp.FechaPago = &parsed
|
|
}
|
|
}
|
|
cp.TransaccionID = req.TransaccionID
|
|
cp.Notas = req.Notas
|
|
if err := models.UpdateCuentaPagar(cp); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
func MarcarCuentaPagarPagada(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
var req struct {
|
|
FechaPago string `json:"fecha_pago"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
fecha := time.Now()
|
|
if req.FechaPago != "" {
|
|
if parsed, err := time.Parse("2006-01-02", req.FechaPago); err == nil {
|
|
fecha = parsed
|
|
}
|
|
}
|
|
if err := models.MarcarCuentaPagarPagada(uint(id), fecha); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// GenerarDocumentoCuentaCobro produce el PDF de solicitud de cuenta de cobro a
|
|
// partir de la plantilla activa tipo 'cuenta_cobro'.
|
|
// POST /api/v2/contabilidad/cuentas-cobro/:id/generar-documento
|
|
func GenerarDocumentoCuentaCobro(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
doc, err := services.GenerarDocumentoCuentaCobro(uint(id), generadoPorFromContext(c))
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.Status(201).JSON(fiber.Map{"ok": true, "documento": doc})
|
|
}
|
|
|
|
func DeleteCuentaPagar(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
if err := models.DeleteCuentaPagar(uint(id)); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// DownloadCuentaPagarArchivo sirve el soporte de una factura de compra
|
|
// (subida desde el dashboard o adjuntada por Telegram).
|
|
func DownloadCuentaPagarArchivo(c *fiber.Ctx) error {
|
|
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
|
}
|
|
cp, err := models.GetCuentaPagarByID(uint(id))
|
|
if err != nil || cp.Archivo == "" {
|
|
return c.Status(404).JSON(fiber.Map{"error": "Archivo no encontrado"})
|
|
}
|
|
clean := filepath.Clean(cp.Archivo)
|
|
if !strings.HasPrefix(clean, "uploads/") {
|
|
return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
|
|
}
|
|
nombre := cp.OriginalName
|
|
if nombre == "" {
|
|
nombre = filepath.Base(clean)
|
|
}
|
|
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, nombre))
|
|
return c.SendFile(clean)
|
|
}
|