372 lines
11 KiB
Go
372 lines
11 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"consumer/internal/logging"
|
|
"consumer/internal/models/entidad_db"
|
|
)
|
|
|
|
// Constantes y errores
|
|
const dbQueryTimeout = 5 * time.Second
|
|
|
|
var (
|
|
// Custom errors
|
|
ErrInvalidUUID = errors.New("ID de transacción inválido")
|
|
ErrInvalidMessage = errors.New("mensaje inválido")
|
|
ErrCompanyNotFound = errors.New("empresa no encontrada")
|
|
ErrAlreadyProcessed = errors.New("factura ya procesada y enviada a DLQ")
|
|
ErrNilPointer = errors.New("puntero nulo encontrado en los datos")
|
|
)
|
|
|
|
// DBManager maneja las operaciones de base de datos
|
|
type DBManager struct {
|
|
db *sql.DB
|
|
logger *logging.LoggerSystem
|
|
}
|
|
|
|
// NewDBManager crea un nuevo gestor de base de datos
|
|
func NewDBManager(dsn string, logger *logging.LoggerSystem) (*DBManager, error) {
|
|
db, err := sql.Open("postgres", dsn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error abriendo conexión a base de datos: %w", err)
|
|
}
|
|
|
|
// Configure connection pool
|
|
db.SetMaxOpenConns(20)
|
|
db.SetMaxIdleConns(10)
|
|
db.SetConnMaxLifetime(time.Hour)
|
|
|
|
// Test connection
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
if err := db.PingContext(ctx); err != nil {
|
|
return nil, fmt.Errorf("error pingueando base de datos: %w", err)
|
|
}
|
|
|
|
return &DBManager{
|
|
db: db,
|
|
logger: logger,
|
|
}, nil
|
|
}
|
|
|
|
// Close cierra la conexión a la base de datos
|
|
func (dm *DBManager) Close() error {
|
|
return dm.db.Close()
|
|
}
|
|
|
|
// isFacturaInDLQ comprueba si una factura ya fue procesada y enviada a DLQ
|
|
func (dm *DBManager) IsFacturaInDLQ(ctx context.Context, id string) (bool, error) {
|
|
var exists bool
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
err := dm.db.QueryRowContext(ctx,
|
|
"SELECT EXISTS(SELECT 1 FROM facturacion_dlq WHERE transaccion_id = $1)",
|
|
id).Scan(&exists)
|
|
|
|
if err != nil {
|
|
return false, fmt.Errorf("error verificando DLQ: %w", err)
|
|
}
|
|
|
|
return exists, nil
|
|
}
|
|
|
|
// getDatosFacturaEmpresa obtiene los datos de registro de empresa necesarios para la factura
|
|
func (dm *DBManager) GetDatosFacturaEmpresa(
|
|
ctx context.Context,
|
|
nit int64,
|
|
codigoSucursal int,
|
|
codigoPuntoVenta int,
|
|
) (entidad_db.DatosFactura, error) {
|
|
var datosFactura entidad_db.DatosFactura
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
const queryEmpresa = `
|
|
SELECT re.id, re.codigo_ambiente, re.codigo_modalidad, re.codigo_punto_venta,
|
|
re.codigo_sistema, re.codigo_sucursal, re.nit,
|
|
cu.cuis, cf.codigo, re.token_key, re.token_value, re.nombre_archivo_certificado, re.nombre_archivo_clave_privada
|
|
FROM registroEmpresa re
|
|
JOIN cuis cu ON cu.registro_empresa_id = re.id
|
|
JOIN cufd cf ON cf.cuis_id = cu.id
|
|
WHERE re.nit = $1 AND re.codigo_sucursal = $2 AND re.codigo_punto_venta = $3
|
|
ORDER BY cu.fecha_vigencia DESC, cf.fecha_vigencia DESC
|
|
LIMIT 1`
|
|
|
|
if err := dm.db.QueryRowContext(ctx, queryEmpresa,
|
|
nit, codigoSucursal, codigoPuntoVenta,
|
|
).Scan(
|
|
&datosFactura.RegistroEmpresaID,
|
|
&datosFactura.CodigoAmbiente,
|
|
&datosFactura.CodigoModalidad,
|
|
&datosFactura.CodigoPuntoVenta,
|
|
&datosFactura.CodigoSistema,
|
|
&datosFactura.CodigoSucursal,
|
|
&datosFactura.NIT,
|
|
&datosFactura.CUIS,
|
|
&datosFactura.CUFD,
|
|
&datosFactura.TokenKey,
|
|
&datosFactura.TokenValue,
|
|
&datosFactura.NombreArchivoCertificado,
|
|
&datosFactura.NombreArchivoClavePrivada,
|
|
); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return datosFactura, ErrCompanyNotFound
|
|
}
|
|
return datosFactura, fmt.Errorf("error buscando registroEmpresa: %w", err)
|
|
}
|
|
|
|
return datosFactura, nil
|
|
}
|
|
|
|
// getCUISDatosFactura obtiene el CUIS y datos relacionados
|
|
func (dm *DBManager) GetCUISDatosFactura(ctx context.Context, registroEmpresaID int) (int, string, string, error) {
|
|
var cuisID int
|
|
var cufd, codControl string
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
// Obtener CUIS ID
|
|
if err := dm.db.QueryRowContext(ctx,
|
|
`SELECT id FROM cuis WHERE registro_empresa_id = $1 ORDER BY fecha_vigencia DESC LIMIT 1`,
|
|
registroEmpresaID,
|
|
).Scan(&cuisID); err != nil {
|
|
return 0, "", "", fmt.Errorf("error buscando cuisID: %w", err)
|
|
}
|
|
|
|
// Obtener CUFD y código de control
|
|
if err := dm.db.QueryRowContext(ctx,
|
|
"SELECT codigo, codigo_control FROM cufd WHERE cuis_id = $1",
|
|
cuisID,
|
|
).Scan(&cufd, &codControl); err != nil {
|
|
return cuisID, "", "", fmt.Errorf("error buscando cufd: %w", err)
|
|
}
|
|
|
|
return cuisID, cufd, codControl, nil
|
|
}
|
|
|
|
// verificarExistenciaFactura verifica si una factura existe y obtiene sus datos
|
|
func (dm *DBManager) VerificarExistenciaFactura(ctx context.Context, id string) (
|
|
bool, string, string, time.Time, error) {
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
var exists bool
|
|
if err := dm.db.QueryRowContext(ctx,
|
|
"SELECT EXISTS(SELECT 1 FROM facturacion_facturas WHERE id=$1)",
|
|
id,
|
|
).Scan(&exists); err != nil {
|
|
return false, "", "", time.Time{}, fmt.Errorf("error verificando existencia: %w", err)
|
|
}
|
|
|
|
if !exists {
|
|
return false, "", "", time.Time{}, nil
|
|
}
|
|
|
|
var numeroFactura, estado string
|
|
var fechaEmision time.Time
|
|
|
|
err := dm.db.QueryRowContext(ctx, `
|
|
SELECT fecha_emision, estado
|
|
FROM facturacion_facturas WHERE id = $1`, id,
|
|
).Scan(&fechaEmision, &estado)
|
|
|
|
if err != nil {
|
|
return true, "", "", time.Time{}, fmt.Errorf("error obteniendo datos: %w", err)
|
|
}
|
|
|
|
return true, numeroFactura, estado, fechaEmision, nil
|
|
}
|
|
|
|
// registra una interacción con el servicio SOAP
|
|
func (dm *DBManager) RegistrarInteraccionServicio(ctx context.Context, id, tipoServicio, endpoint, reqBody, respBody string,
|
|
statusCode int, duracion int64, exitoso bool, vMsgRespSoap string) error {
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
_, err := dm.db.ExecContext(ctx, `
|
|
INSERT INTO facturacion_servicio_interacciones
|
|
(factura_id, tipo_servicio, endpoint, request_body, response_body, status_code, duracion_ms, exitoso, msg_soap)
|
|
VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
id, tipoServicio, endpoint, reqBody, respBody, statusCode, duracion, exitoso, vMsgRespSoap)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("error registrando interacción: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// crea un registro de factura pendiente
|
|
func (dm *DBManager) CrearFacturaPendiente(ctx context.Context, id string,
|
|
fechaEmision time.Time, estado string, vCuf string, vUrlFact string, vEmpresaID int, vCodDocSector int, vFactura string) error {
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
_, err := dm.db.ExecContext(ctx, `
|
|
INSERT INTO facturacion_facturas
|
|
(id, fecha_emision, estado, cuf, url, registro_empresa_id, codigo_documento_sector, factura)
|
|
VALUES($1,$2,$3,$4,$5,$6,$7,$8)`, id, fechaEmision, estado, vCuf, vUrlFact, vEmpresaID, vCodDocSector, vFactura)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("error creando factura pendiente: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// obtiene el CUIS y datos relacionados
|
|
func (dm *DBManager) GetFacturacionFacturas(ctx context.Context, id_transaccion string) (string, int, string, error) {
|
|
var vRegEmpresaID int
|
|
var vCuf string
|
|
var vEstado string
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
// Obtenemos el ID del registro de la empresa
|
|
if err := dm.db.QueryRowContext(ctx,
|
|
`SELECT cuf, registro_empresa_id, estado FROM facturacion_facturas WHERE id = $1`, id_transaccion,
|
|
).Scan(&vCuf, &vRegEmpresaID, &vEstado); err != nil {
|
|
return "", 0, "", fmt.Errorf("error al buscar registro_empresa_id: %w", err)
|
|
}
|
|
|
|
return vCuf, vRegEmpresaID, vEstado, nil
|
|
}
|
|
|
|
// Obtiene los datos de la tabla RegistroEmpresa, cui, cufd
|
|
func (dm *DBManager) GetRegEmpresaCuisCuf(ctx context.Context, idRegCompra int) (entidad_db.DatosFactura, error) {
|
|
var datosFactura entidad_db.DatosFactura
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
const queryEmpresa = `
|
|
SELECT re.id, re.codigo_ambiente, re.codigo_modalidad, re.codigo_punto_venta,
|
|
re.codigo_sistema, re.codigo_sucursal, re.nit,
|
|
cu.cuis, cf.codigo, cf.codigo_control, re.token_key, re.token_value, re.nombre_archivo_clave_privada, re.nombre_archivo_certificado
|
|
FROM registroEmpresa re
|
|
JOIN cuis cu ON cu.registro_empresa_id = re.id
|
|
JOIN cufd cf ON cf.cuis_id = cu.id
|
|
WHERE re.id = $1 ORDER BY cu.fecha_vigencia DESC, cf.fecha_vigencia DESC LIMIT 1`
|
|
|
|
if err := dm.db.QueryRowContext(
|
|
ctx, queryEmpresa, idRegCompra,
|
|
).Scan(
|
|
&datosFactura.RegistroEmpresaID,
|
|
&datosFactura.CodigoAmbiente,
|
|
&datosFactura.CodigoModalidad,
|
|
&datosFactura.CodigoPuntoVenta,
|
|
&datosFactura.CodigoSistema,
|
|
&datosFactura.CodigoSucursal,
|
|
&datosFactura.NIT,
|
|
&datosFactura.CUIS,
|
|
&datosFactura.CUFD,
|
|
&datosFactura.CodigoControl,
|
|
&datosFactura.TokenKey,
|
|
&datosFactura.TokenValue,
|
|
&datosFactura.NombreArchivoClavePrivada,
|
|
&datosFactura.NombreArchivoCertificado,
|
|
); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return datosFactura, ErrCompanyNotFound
|
|
}
|
|
return datosFactura, fmt.Errorf("error buscando tablas registroEmpresa, cuis, cufd: %w", err)
|
|
}
|
|
|
|
return datosFactura, nil
|
|
}
|
|
|
|
// actualizarEstadoFactura actualiza el estado de una factura
|
|
func (dm *DBManager) ActualizarEstadoFactura(ctx context.Context, id, nuevoEstado, codigoAutorizacion string,
|
|
estadoAnterior string, detalles string, vFacturaFirmada string) error {
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
tx, err := dm.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("error iniciando transacción: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
var updateQuery string
|
|
var params []interface{}
|
|
|
|
if codigoAutorizacion != "" {
|
|
updateQuery = `UPDATE facturacion_facturas SET estado=$1, codigo_autorizacion=$2 WHERE id=$3`
|
|
params = []interface{}{nuevoEstado, codigoAutorizacion, id}
|
|
} else {
|
|
if vFacturaFirmada != "" {
|
|
updateQuery = `UPDATE facturacion_facturas SET estado=$1, factura_firmada=$2 WHERE id=$3`
|
|
params = []interface{}{nuevoEstado, vFacturaFirmada, id}
|
|
} else {
|
|
updateQuery = `UPDATE facturacion_facturas SET estado=$1 WHERE id=$2`
|
|
params = []interface{}{nuevoEstado, id}
|
|
}
|
|
}
|
|
|
|
if _, err = tx.ExecContext(ctx, updateQuery, params...); err != nil {
|
|
return fmt.Errorf("error actualizando estado: %w", err)
|
|
}
|
|
|
|
if _, err = tx.ExecContext(ctx, `
|
|
INSERT INTO facturacion_eventos_factura
|
|
(factura_id, estado_anterior, estado_nuevo, detalles)
|
|
VALUES($1,$2,$3,$4)`,
|
|
id, estadoAnterior, nuevoEstado, detalles); err != nil {
|
|
return fmt.Errorf("error registrando evento: %w", err)
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
return fmt.Errorf("error haciendo commit: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// inserta un mensaje en la tabla de DLQ
|
|
func (dm *DBManager) InsertarEnDLQ(ctx context.Context, id string, mensaje string, estadoSoap string) error {
|
|
ctx, cancel := context.WithTimeout(ctx, dbQueryTimeout)
|
|
defer cancel()
|
|
|
|
// Verificar si ya existe
|
|
var exists bool
|
|
if err := dm.db.QueryRowContext(ctx,
|
|
"SELECT EXISTS(SELECT 1 FROM facturacion_dlq WHERE transaccion_id = $1)",
|
|
id).Scan(&exists); err != nil {
|
|
return fmt.Errorf("error verificando DLQ: %w", err)
|
|
}
|
|
|
|
// Si ya existe, no insertar nuevamente
|
|
if exists {
|
|
return nil
|
|
}
|
|
|
|
_, err := dm.db.ExecContext(ctx,
|
|
"INSERT INTO facturacion_dlq(transaccion_id, mensaje, estado) VALUES($1,$2,$3)",
|
|
id, mensaje, estadoSoap)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("error insertando en DLQ: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|