sistema de facturacion
This commit is contained in:
commit
ae0e2e7155
82
README.md
Normal file
82
README.md
Normal file
@ -0,0 +1,82 @@
|
||||
# comando para crear carpetas y archivos
|
||||
mkdir -p daemonService/{cmd,config,models,db,notifications,services,api,scheduler} && touch daemonService/cmd/main.go daemonService/config/config.go daemonService/models/models.go daemonService/db/db.go daemonService/notifications/notifier.go daemonService/services/service.go daemonService/services/tipo_documento_identidad.go daemonService/api/handlers.go daemonService/api/server.go daemonService/scheduler/scheduler.go daemonService/config.yaml
|
||||
|
||||
myproject/
|
||||
├── cmd/
|
||||
│ └── main.go
|
||||
├── config/
|
||||
│ └── config.go
|
||||
├── models/
|
||||
│ └── models.go
|
||||
├── db/
|
||||
│ └── db.go
|
||||
├── notifications/
|
||||
│ └── notifier.go
|
||||
├── services/
|
||||
│ ├── service.go
|
||||
│ └── tipo_documento_identidad.go
|
||||
├── api/
|
||||
│ ├── handlers.go
|
||||
│ └── server.go
|
||||
├── scheduler/
|
||||
│ └── scheduler.go
|
||||
└── config.yaml
|
||||
|
||||
|
||||
|
||||
|
||||
.
|
||||
├── configs
|
||||
│ └── config.yaml
|
||||
├── cmd
|
||||
│ └── daemon
|
||||
│ └── main.go
|
||||
└── internal
|
||||
├── api
|
||||
│ └── router.go
|
||||
├── config
|
||||
│ └── config.go
|
||||
├── db
|
||||
│ └── db.go
|
||||
├── notifications
|
||||
│ └── notifications.go
|
||||
├── services
|
||||
│ ├── service.go
|
||||
│ └── tipo_documento_identidad.go
|
||||
└── soap
|
||||
└── soap.go
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# iniciar go
|
||||
go mod init daemonService
|
||||
go mod tidy
|
||||
# para ejecutar directo
|
||||
go run cmd/main.go
|
||||
# eliminar archivos go.sum go.mod
|
||||
go clean -modcache
|
||||
rm -r go.sum go.mod
|
||||
|
||||
|
||||
# 1. Compilar el código
|
||||
go build -o daemon-services cmd/daemon/main.go
|
||||
|
||||
# para compilar en linux
|
||||
env GOOS=linux GOARCH=amd64 go build -o daemon-services cmd/daemon/main.go
|
||||
|
||||
# 2. Ejecutar el servicio
|
||||
./daemon-services
|
||||
|
||||
# Para ejecutarlo con opciones específicas:
|
||||
# Usando un archivo de configuración específico
|
||||
./daemon-services --config=mi-config.yaml
|
||||
|
||||
# Ejecutar una vez y salir
|
||||
./daemon-services --run-once
|
||||
|
||||
# Ejecutar solo un servicio específico
|
||||
./daemon-services --service=tipo_documento_identidad
|
8
api-soap-facturacion/.idea/.gitignore
generated
vendored
Normal file
8
api-soap-facturacion/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
9
api-soap-facturacion/.idea/api-soap-facturacion.iml
generated
Normal file
9
api-soap-facturacion/.idea/api-soap-facturacion.iml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
api-soap-facturacion/.idea/modules.xml
generated
Normal file
8
api-soap-facturacion/.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/api-soap-facturacion.iml" filepath="$PROJECT_DIR$/.idea/api-soap-facturacion.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
133
api-soap-facturacion/README.md
Normal file
133
api-soap-facturacion/README.md
Normal file
@ -0,0 +1,133 @@
|
||||
# Proyecto Go para consumo de SOAP APIs
|
||||
|
||||
## Estructura del proyecto
|
||||
|
||||
```
|
||||
soap-api-client/
|
||||
├── cmd/
|
||||
│ └── api/
|
||||
│ └── main.go
|
||||
├── configs/
|
||||
│ └── config.yaml
|
||||
├── internal/
|
||||
│ ├── api/
|
||||
│ │ ├── handler.go
|
||||
│ │ └── router.go
|
||||
│ ├── config/
|
||||
│ │ └── config.go
|
||||
│ ├── database/
|
||||
│ │ ├── postgres.go
|
||||
│ │ └── models.go
|
||||
│ ├── soap/
|
||||
│ │ ├── client.go
|
||||
│ │ ├── api1/
|
||||
│ │ │ ├── client.go
|
||||
│ │ │ ├── models.go
|
||||
│ │ │ └── transformers.go
|
||||
│ │ └── api2/
|
||||
│ │ ├── client.go
|
||||
│ │ ├── models.go
|
||||
│ │ └── transformers.go
|
||||
│ └── logger/
|
||||
│ └── logger.go
|
||||
├── pkg/
|
||||
│ ├── errors/
|
||||
│ │ └── errors.go
|
||||
│ ├── soap/
|
||||
│ │ └── soap.go
|
||||
│ └── utils/
|
||||
│ └── utils.go
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
## Inicialización del proyecto
|
||||
|
||||
```bash
|
||||
mkdir -p soap-api-client/cmd/api
|
||||
mkdir -p soap-api-client/configs
|
||||
mkdir -p soap-api-client/internal/api
|
||||
mkdir -p soap-api-client/internal/configs
|
||||
mkdir -p soap-api-client/internal/database
|
||||
mkdir -p soap-api-client/internal/soap/api1
|
||||
mkdir -p soap-api-client/internal/soap/api2
|
||||
mkdir -p soap-api-client/internal/logger
|
||||
mkdir -p soap-api-client/pkg/errors
|
||||
mkdir -p soap-api-client/pkg/soap
|
||||
mkdir -p soap-api-client/pkg/utils
|
||||
cd soap-api-client
|
||||
go mod init github.com/yourusername/soap-api-client
|
||||
```
|
||||
|
||||
|
||||
## Comandos para la ejecución
|
||||
|
||||
```bash
|
||||
# Iniciar la aplicación
|
||||
go run cmd/api/main.go
|
||||
|
||||
# Construir la aplicación
|
||||
go build -o soap-api-client cmd/api/main.go
|
||||
|
||||
# Ejecutar la aplicación construida
|
||||
./soap-api-client
|
||||
```
|
||||
|
||||
## Comandos para la ejecución
|
||||
|
||||
```bash
|
||||
# Iniciar la aplicación
|
||||
go run cmd/api/main.go
|
||||
|
||||
# Construir la aplicación
|
||||
go build -o soap-api-client cmd/api/main.go
|
||||
|
||||
# Ejecutar la aplicación construida
|
||||
./soap-api-client
|
||||
```
|
||||
|
||||
## Ejemplos de uso
|
||||
|
||||
### Ejemplo de solicitud GetData a API1
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:9999/api/v1/api1/get-data \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"id": "12345",
|
||||
"type": "CUSTOMER",
|
||||
"date": "2023-12-01"
|
||||
}'
|
||||
```
|
||||
|
||||
### Ejemplo de solicitud de transacción a API2
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:9999/api/v1/api2/process-transaction \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"transactionId": "TX789012",
|
||||
"accountId": "ACC123",
|
||||
"amount": 100.50,
|
||||
"currency": "USD",
|
||||
"description": "Pago de servicio"
|
||||
}'
|
||||
```
|
||||
|
||||
### Ejemplo de obtención de parámetros
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:9999/api/v1/parameters
|
||||
```
|
||||
|
||||
### Ejemplo de actualización de parámetro
|
||||
|
||||
```bash
|
||||
curl -X PUT http://localhost:9999/api/v1/parameters/soap.api1.endpoint \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"value": "https://new-api1.example.com/soap",
|
||||
"description": "Nuevo endpoint para API 1",
|
||||
"active": true
|
||||
}'
|
||||
```
|
165
api-soap-facturacion/cmd/api/main.go
Normal file
165
api-soap-facturacion/cmd/api/main.go
Normal file
@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"api-soap-facturacion/internal/api"
|
||||
"api-soap-facturacion/internal/config"
|
||||
"api-soap-facturacion/internal/database"
|
||||
"api-soap-facturacion/internal/logger"
|
||||
"api-soap-facturacion/internal/soap"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Inicializar configuración
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error al cargar la configuración: %v", err))
|
||||
}
|
||||
|
||||
// Inicializar logger
|
||||
log := logger.NewLogger(cfg)
|
||||
//log.Info("Iniciando aplicación", logger.Field("app", cfg.App.Name))
|
||||
log.Info("Iniciando aplicación", logger.NewField("app", cfg.App.Name))
|
||||
|
||||
// Conectar a la base de datos
|
||||
db, err := database.NewPostgresConnection(cfg.Database)
|
||||
if err != nil {
|
||||
//log.Fatal("Error al conectar a la base de datos", logger.Field("error", err))
|
||||
log.Fatal("Error al conectar a la base de datos", logger.NewField("error", err))
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Verificar conexión a la base de datos
|
||||
if err := db.Ping(); err != nil {
|
||||
log.Error("Error al cargar parámetros desde la base de datos", logger.NewField("error", err))
|
||||
}
|
||||
log.Info("Conexión a la base de datos establecida")
|
||||
|
||||
// Cargar parámetros desde la base de datos
|
||||
params, err := database.LoadParameters(db)
|
||||
if err != nil {
|
||||
log.Info("Parámetros cargados desde la base de datos", logger.NewField("count", len(params)))
|
||||
} else {
|
||||
log.Info("Parámetros cargados desde la base de datos", logger.NewField("count", len(params)))
|
||||
// Actualizar configuración con parámetros de la base de datos
|
||||
cfg.MergeParameters(params)
|
||||
}
|
||||
|
||||
// Inicializar cliente SOAP
|
||||
soapClients := soap.NewSoapClients(cfg, log)
|
||||
|
||||
// Inicializar API
|
||||
router := api.NewRouter(cfg, log, db, soapClients)
|
||||
|
||||
// Configurar servidor HTTP
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", cfg.App.Port),
|
||||
Handler: router,
|
||||
ReadTimeout: cfg.App.RequestTimeout * time.Second,
|
||||
WriteTimeout: cfg.App.RequestTimeout * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
|
||||
// Iniciar servidor en goroutine
|
||||
go func() {
|
||||
log.Info("Servidor iniciado", logger.NewField("port", cfg.App.Port))
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatal("Error al iniciar servidor", logger.NewField("error", err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Configurar captura de señales para graceful shutdown
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
log.Info("Apagando servidor...")
|
||||
|
||||
// Iniciar graceful shutdown
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Fatal("Error al apagar servidor", logger.NewField("error", err))
|
||||
}
|
||||
log.Info("Servidor detenido correctamente")
|
||||
}
|
||||
|
||||
//
|
||||
//func main() {
|
||||
// // Inicializar configuración
|
||||
// cfg, err := config.LoadConfig()
|
||||
// if err != nil {
|
||||
// panic(fmt.Sprintf("Error al cargar la configuración: %v", err))
|
||||
// }
|
||||
//
|
||||
// // Inicializar logger
|
||||
// log := logger.NewLogger(cfg)
|
||||
// //log.Info("Iniciando aplicación", logger.Field("app", cfg.App.Name))
|
||||
// log.Info("Iniciando aplicación", logger.NewField("app", cfg.App.Name))
|
||||
//
|
||||
// // Conectar a la base de datos
|
||||
// db, err := database.NewPostgresConnection(cfg.Database)
|
||||
// if err != nil {
|
||||
// //log.Fatal("Error al conectar a la base de datos", logger.Field("error", err))
|
||||
// log.Fatal("Error al conectar a la base de datos", logger.NewField("error", err))
|
||||
// }
|
||||
// defer db.Close()
|
||||
//
|
||||
// // Verificar conexión a la base de datos
|
||||
// if err := db.Ping(); err != nil {
|
||||
// log.Error("Error al cargar parámetros desde la base de datos", logger.NewField("error", err))
|
||||
// }
|
||||
// log.Info("Conexión a la base de datos establecida")
|
||||
//
|
||||
// // Cargar parámetros desde la base de datos
|
||||
// params, err := database.LoadParameters(db)
|
||||
// if err != nil {
|
||||
// log.Info("Parámetros cargados desde la base de datos", logger.NewField("count", len(params)))
|
||||
// } else {
|
||||
// log.Info("Parámetros cargados desde la base de datos", logger.NewField("count", len(params)))
|
||||
// // Actualizar configuración con parámetros de la base de datos
|
||||
// cfg.MergeParameters(params)
|
||||
// }
|
||||
//
|
||||
// // Inicializar cliente SOAP
|
||||
// soapClients := soap.NewSoapClients(cfg, log)
|
||||
//
|
||||
// // Inicializar API
|
||||
// router := api.NewRouter(cfg, log, db, soapClients)
|
||||
//
|
||||
// // Configurar servidor HTTP
|
||||
// srv := &http.Server{
|
||||
// Addr: fmt.Sprintf(":%d", cfg.App.Port),
|
||||
// Handler: router,
|
||||
// ReadTimeout: cfg.App.RequestTimeout * time.Second,
|
||||
// WriteTimeout: cfg.App.RequestTimeout * time.Second,
|
||||
// IdleTimeout: 120 * time.Second,
|
||||
// }
|
||||
//
|
||||
// // Iniciar servidor en goroutine
|
||||
// go func() {
|
||||
// log.Info("Servidor iniciado", logger.NewField("port", cfg.App.Port))
|
||||
// if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
// log.Fatal("Error al iniciar servidor", logger.NewField("error", err))
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// // Configurar captura de señales para graceful shutdown
|
||||
// quit := make(chan os.Signal, 1)
|
||||
// signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
// <-quit
|
||||
// log.Info("Apagando servidor...")
|
||||
//
|
||||
// // Iniciar graceful shutdown
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
// defer cancel()
|
||||
// if err := srv.Shutdown(ctx); err != nil {
|
||||
// log.Fatal("Error al apagar servidor", logger.NewField("error", err))
|
||||
// }
|
||||
// log.Info("Servidor detenido correctamente")
|
||||
//}
|
36
api-soap-facturacion/configs/config.yaml
Normal file
36
api-soap-facturacion/configs/config.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
app:
|
||||
name: "api-facturacion-myapps"
|
||||
port: 9999
|
||||
environment: "development" # development, staging, production
|
||||
log_level: "debug" # debug, info, warn, error
|
||||
request_timeout: 30 # seconds
|
||||
log_file_path: "./logs/api_facturacion.log"
|
||||
log_max_size: 10 # Tamaño máximo en MB
|
||||
log_max_backups: 5 # Número de archivos rotados a mantener
|
||||
log_max_age: 30 # Días a mantener los archivos
|
||||
log_compress: true # Comprimir archivos rotados
|
||||
use_file_logger: true # Controla si usar archivo (true) o solo stdout (false)
|
||||
|
||||
database:
|
||||
host: "localhost"
|
||||
port: 5555
|
||||
username: "facturacion_user"
|
||||
password: "facturacion_pass"
|
||||
database: "facturacion_bd"
|
||||
max_open_conns: 10
|
||||
max_idle_conns: 5
|
||||
conn_max_lifetime: 1h
|
||||
|
||||
soap:
|
||||
timeout: 15 # seconds
|
||||
apis:
|
||||
api1:
|
||||
endpoint: "https://api1.example.com/soap"
|
||||
username: ""
|
||||
password: ""
|
||||
timeout: 10 # seconds
|
||||
api2:
|
||||
endpoint: "https://api2.example.com/soap"
|
||||
username: ""
|
||||
password: ""
|
||||
timeout: 20 # seconds
|
56
api-soap-facturacion/go.mod
Normal file
56
api-soap-facturacion/go.mod
Normal file
@ -0,0 +1,56 @@
|
||||
module api-soap-facturacion
|
||||
|
||||
go 1.23.9
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.7.4
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/viper v1.20.1
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
144
api-soap-facturacion/go.sum
Normal file
144
api-soap-facturacion/go.sum
Normal file
@ -0,0 +1,144 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
|
||||
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
66
api-soap-facturacion/internal/api/getDataHandler.go
Normal file
66
api-soap-facturacion/internal/api/getDataHandler.go
Normal file
@ -0,0 +1,66 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"api-soap-facturacion/internal/logger"
|
||||
api1 "api-soap-facturacion/internal/soap/api"
|
||||
soapPkg "api-soap-facturacion/pkg/soap"
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// API1GetData maneja solicitudes GetData para API1
|
||||
func (h *Handler) API1GetData(c *gin.Context) {
|
||||
// Obtener logger
|
||||
log := getLogger(c)
|
||||
|
||||
// Obtener contexto con timeout
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), h.config.App.RequestTimeout*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Agregar ID de solicitud al contexto
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
ctx = context.WithValue(ctx, soapPkg.RequestIDKey{}, requestID)
|
||||
|
||||
// Parsear solicitud
|
||||
var requestData struct {
|
||||
ID string `json:"id" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||
log.Error("Error al parsear solicitud", logger.NewField("error", err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Error al parsear solicitud",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Crear solicitud SOAP
|
||||
request := api1.GetDataRequest{
|
||||
Namespace: "http://api1.example.com/soap",
|
||||
ID: requestData.ID,
|
||||
Type: requestData.Type,
|
||||
Date: requestData.Date,
|
||||
}
|
||||
|
||||
// Llamar a API1
|
||||
response, err := h.soapClients.API1.GetData(ctx, request)
|
||||
if err != nil {
|
||||
log.Error("Error al llamar a API1", logger.NewField("error", err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Error al realizar solicitud a API1",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Retornar respuesta
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": response.Status,
|
||||
"resultCode": response.ResultCode,
|
||||
"description": response.Description,
|
||||
"data": response.Data,
|
||||
})
|
||||
}
|
240
api-soap-facturacion/internal/api/handler.go
Normal file
240
api-soap-facturacion/internal/api/handler.go
Normal file
@ -0,0 +1,240 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"api-soap-facturacion/internal/config"
|
||||
"api-soap-facturacion/internal/database"
|
||||
"api-soap-facturacion/internal/logger"
|
||||
"api-soap-facturacion/internal/soap"
|
||||
"api-soap-facturacion/internal/soap/api2"
|
||||
//"api-soap-facturacion/pkg/errors"
|
||||
soapPkg "api-soap-facturacion/pkg/soap"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// Handler maneja las solicitudes HTTP
|
||||
type Handler struct {
|
||||
config *config.Config
|
||||
logger logger.Logger
|
||||
db *sqlx.DB
|
||||
soapClients *soap.Clients
|
||||
}
|
||||
|
||||
// NewHandler crea un nuevo handler
|
||||
func NewHandler(cfg *config.Config, log logger.Logger, db *sqlx.DB, soapClients *soap.Clients) *Handler {
|
||||
return &Handler{
|
||||
config: cfg,
|
||||
logger: log,
|
||||
db: db,
|
||||
soapClients: soapClients,
|
||||
}
|
||||
}
|
||||
|
||||
// HealthCheck verifica el estado de la aplicación
|
||||
func (h *Handler) HealthCheck(c *gin.Context) {
|
||||
// Verificar conexión a la base de datos
|
||||
dbErr := h.db.Ping()
|
||||
|
||||
status := "ok"
|
||||
statusCode := http.StatusOK
|
||||
if dbErr != nil {
|
||||
status = "degraded"
|
||||
statusCode = http.StatusServiceUnavailable
|
||||
}
|
||||
|
||||
c.JSON(statusCode, gin.H{
|
||||
"status": status,
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
"version": "1.0.0",
|
||||
"services": gin.H{
|
||||
"database": dbErr == nil,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// API2ProcessTransaction maneja solicitudes de transacción para API2
|
||||
func (h *Handler) API2ProcessTransaction(c *gin.Context) {
|
||||
// Obtener logger
|
||||
log := getLogger(c)
|
||||
|
||||
// Obtener contexto con timeout
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), h.config.App.RequestTimeout*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Agregar ID de solicitud al contexto
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
ctx = context.WithValue(ctx, soapPkg.RequestIDKey{}, requestID)
|
||||
|
||||
// Parsear solicitud
|
||||
var requestData struct {
|
||||
TransactionID string `json:"transactionId" binding:"required"`
|
||||
AccountID string `json:"accountId" binding:"required"`
|
||||
Amount float64 `json:"amount" binding:"required"`
|
||||
Currency string `json:"currency" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||
log.Error("Error al parsear solicitud", logger.NewField("error", err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Error al parsear solicitud",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Crear solicitud SOAP
|
||||
request := api2.TransactionRequest{
|
||||
Namespace: "http://api2.example.com/soap",
|
||||
TransactionID: requestData.TransactionID,
|
||||
AccountID: requestData.AccountID,
|
||||
Amount: requestData.Amount,
|
||||
Currency: requestData.Currency,
|
||||
Description: requestData.Description,
|
||||
}
|
||||
|
||||
// Llamar a API2
|
||||
response, err := h.soapClients.API2.ProcessTransaction(ctx, request)
|
||||
if err != nil {
|
||||
log.Error("Error al llamar a API2", logger.NewField("error", err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Error al realizar solicitud a API2",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Retornar respuesta
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"resultCode": response.ResultCode,
|
||||
"resultMessage": response.ResultMessage,
|
||||
"transactionId": response.TransactionID,
|
||||
"confirmationCode": response.ConfirmationCode,
|
||||
"timestamp": response.Timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAllParameters retorna todos los parámetros
|
||||
func (h *Handler) GetAllParameters(c *gin.Context) {
|
||||
// Obtener logger
|
||||
log := getLogger(c)
|
||||
|
||||
// Consultar parámetros
|
||||
var params []database.Parameter
|
||||
query := `SELECT id, key, value, description, active, created_at, updated_at FROM parameters`
|
||||
|
||||
err := h.db.Select(¶ms, query)
|
||||
if err != nil {
|
||||
log.Error("Error al obtener parámetros", logger.NewField("error", err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Error al obtener parámetros",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Retornar parámetros
|
||||
c.JSON(http.StatusOK, params)
|
||||
}
|
||||
|
||||
// GetParameter retorna un parámetro por clave
|
||||
func (h *Handler) GetParameter(c *gin.Context) {
|
||||
// Obtener logger
|
||||
log := getLogger(c)
|
||||
|
||||
// Obtener clave
|
||||
key := c.Param("key")
|
||||
|
||||
// Consultar parámetro
|
||||
var param database.Parameter
|
||||
query := `SELECT id, key, value, description, active, created_at, updated_at FROM parameters WHERE key = $1`
|
||||
|
||||
err := h.db.Get(¶m, query, key)
|
||||
if err != nil {
|
||||
log.Error("Error al obtener parámetro",
|
||||
logger.NewField("error", err),
|
||||
logger.NewField("key", key),
|
||||
)
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "Parámetro no encontrado",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Retornar parámetro
|
||||
c.JSON(http.StatusOK, param)
|
||||
}
|
||||
|
||||
// UpdateParameter actualiza un parámetro
|
||||
func (h *Handler) UpdateParameter(c *gin.Context) {
|
||||
// Obtener logger
|
||||
log := getLogger(c)
|
||||
|
||||
// Obtener clave
|
||||
key := c.Param("key")
|
||||
|
||||
// Parsear solicitud
|
||||
var requestData struct {
|
||||
Value string `json:"value" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||
log.Error("Error al parsear solicitud", logger.NewField("error", err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Error al parsear solicitud",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Actualizar parámetro
|
||||
query := `
|
||||
UPDATE parameters
|
||||
SET value = $1, description = $2, active = $3, updated_at = NOW()
|
||||
WHERE key = $4
|
||||
RETURNING id, key, value, description, active, created_at, updated_at
|
||||
`
|
||||
|
||||
var param database.Parameter
|
||||
err := h.db.Get(¶m, query,
|
||||
requestData.Value,
|
||||
requestData.Description,
|
||||
requestData.Active,
|
||||
key,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Error al actualizar parámetro",
|
||||
logger.NewField("error", err),
|
||||
logger.NewField("key", key),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Error al actualizar parámetro",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Retornar parámetro actualizado
|
||||
c.JSON(http.StatusOK, param)
|
||||
}
|
||||
|
||||
// getLogger obtiene el logger del contexto
|
||||
func getLogger(c *gin.Context) logger.Logger {
|
||||
if log, exists := c.Get("logger"); exists {
|
||||
return log.(logger.Logger)
|
||||
}
|
||||
// Si no hay logger en el contexto, retornar un logger por defecto
|
||||
return &defaultLogger{}
|
||||
}
|
||||
|
||||
// defaultLogger es un logger por defecto
|
||||
type defaultLogger struct{}
|
||||
|
||||
func (l *defaultLogger) Debug(msg string, fields ...logger.Field) {}
|
||||
func (l *defaultLogger) Info(msg string, fields ...logger.Field) {}
|
||||
func (l *defaultLogger) Warn(msg string, fields ...logger.Field) {}
|
||||
func (l *defaultLogger) Error(msg string, fields ...logger.Field) {}
|
||||
func (l *defaultLogger) Fatal(msg string, fields ...logger.Field) {}
|
||||
func (l *defaultLogger) With(fields ...logger.Field) logger.Logger { return l }
|
1
api-soap-facturacion/internal/api/registroPuntoVenta.go
Normal file
1
api-soap-facturacion/internal/api/registroPuntoVenta.go
Normal file
@ -0,0 +1 @@
|
||||
package api
|
129
api-soap-facturacion/internal/api/router.go
Normal file
129
api-soap-facturacion/internal/api/router.go
Normal file
@ -0,0 +1,129 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"api-soap-facturacion/internal/config"
|
||||
"api-soap-facturacion/internal/logger"
|
||||
"api-soap-facturacion/internal/soap"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// NewRouter configura el router HTTP
|
||||
func NewRouter(cfg *config.Config, log logger.Logger, db *sqlx.DB, soapClients *soap.Clients) *gin.Engine {
|
||||
// Configurar modo de Gin según entorno
|
||||
if cfg.App.Environment == "production" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
// Crear router
|
||||
router := gin.New()
|
||||
|
||||
// Configurar middleware
|
||||
router.Use(gin.Recovery())
|
||||
router.Use(LoggerMiddleware(log))
|
||||
router.Use(RequestIDMiddleware())
|
||||
router.Use(CORSMiddleware())
|
||||
|
||||
// Configurar manejadores
|
||||
handler := NewHandler(cfg, log, db, soapClients)
|
||||
|
||||
// Rutas de salud
|
||||
router.GET("/health", handler.HealthCheck)
|
||||
|
||||
// Rutas API
|
||||
api := router.Group("/api/v1")
|
||||
{
|
||||
// API1
|
||||
api1 := api.Group("/api1")
|
||||
{
|
||||
api1.POST("/get-data", handler.API1GetData)
|
||||
}
|
||||
|
||||
// API2
|
||||
api2 := api.Group("/api2")
|
||||
{
|
||||
api2.POST("/process-transaction", handler.API2ProcessTransaction)
|
||||
}
|
||||
|
||||
// Parámetros
|
||||
params := api.Group("/parameters")
|
||||
{
|
||||
params.GET("", handler.GetAllParameters)
|
||||
params.GET("/:key", handler.GetParameter)
|
||||
params.PUT("/:key", handler.UpdateParameter)
|
||||
}
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// LoggerMiddleware configura el middleware de logging
|
||||
func LoggerMiddleware(log logger.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Antes de la solicitud
|
||||
path := c.Request.URL.Path
|
||||
method := c.Request.Method
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
|
||||
// Crear logger para esta solicitud
|
||||
reqLogger := log.With(
|
||||
logger.NewField("request_id", requestID),
|
||||
logger.NewField("path", path),
|
||||
logger.NewField("method", method),
|
||||
)
|
||||
|
||||
// Almacenar logger en el contexto
|
||||
c.Set("logger", reqLogger)
|
||||
|
||||
reqLogger.Info("Solicitud iniciada")
|
||||
|
||||
// Procesar solicitud
|
||||
c.Next()
|
||||
|
||||
// Después de la solicitud
|
||||
status := c.Writer.Status()
|
||||
reqLogger.Info("Solicitud completada",
|
||||
logger.NewField("status", status),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// RequestIDMiddleware genera un ID único para cada solicitud
|
||||
func RequestIDMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Si no hay ID de solicitud, generar uno
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
if requestID == "" {
|
||||
requestID = generateRequestID()
|
||||
c.Request.Header.Set("X-Request-ID", requestID)
|
||||
}
|
||||
|
||||
// Establecer ID de solicitud en la respuesta
|
||||
c.Writer.Header().Set("X-Request-ID", requestID)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// CORSMiddleware configura CORS
|
||||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Request-ID")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// generateRequestID genera un ID único para una solicitud
|
||||
func generateRequestID() string {
|
||||
return "req-" + uuid.New().String()
|
||||
}
|
95
api-soap-facturacion/internal/config/config.go
Normal file
95
api-soap-facturacion/internal/config/config.go
Normal file
@ -0,0 +1,95 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config contiene toda la configuración de la aplicación
|
||||
type Config struct {
|
||||
App AppConfig `mapstructure:"app"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
Soap SoapConfig `mapstructure:"soap"`
|
||||
}
|
||||
|
||||
// contiene la configuración general de la aplicación
|
||||
type AppConfig struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Port int `mapstructure:"port"`
|
||||
Environment string `mapstructure:"environment"`
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
RequestTimeout time.Duration `mapstructure:"request_timeout"`
|
||||
LogFilePath string `mapstructure:"log_file_path"`
|
||||
LogMaxSize int `mapstructure:"log_max_size"`
|
||||
LogMaxBackups int `mapstructure:"log_max_backups"`
|
||||
LogMaxAge int `mapstructure:"log_max_age"`
|
||||
LogCompress bool `mapstructure:"log_compress"`
|
||||
}
|
||||
|
||||
// DatabaseConfig contiene la configuración de la base de datos
|
||||
type DatabaseConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Database string `mapstructure:"database"`
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"`
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||||
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
|
||||
}
|
||||
|
||||
// SoapConfig contiene la configuración general para clientes SOAP
|
||||
type SoapConfig struct {
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
APIs map[string]APIConfig `mapstructure:"apis"`
|
||||
}
|
||||
|
||||
// APIConfig contiene la configuración específica para cada API SOAP
|
||||
type APIConfig struct {
|
||||
Endpoint string `mapstructure:"endpoint"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
}
|
||||
|
||||
// LoadConfig carga la configuración desde el archivo config.yaml
|
||||
func LoadConfig() (*Config, error) {
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("./configs")
|
||||
viper.AddConfigPath(".")
|
||||
viper.AutomaticEnv()
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// actualiza la configuración con parámetros de la base de datos
|
||||
func (c *Config) MergeParameters(params map[string]string) {
|
||||
// Ejemplo de sobrescritura de configuración con parámetros de BD
|
||||
if endpoint, ok := params["soap.api1.endpoint"]; ok {
|
||||
if api1Config, exists := c.Soap.APIs["api1"]; exists {
|
||||
api1Config.Endpoint = endpoint
|
||||
c.Soap.APIs["api1"] = api1Config
|
||||
}
|
||||
}
|
||||
|
||||
if endpoint, ok := params["soap.api2.endpoint"]; ok {
|
||||
if api2Config, exists := c.Soap.APIs["api2"]; exists {
|
||||
api2Config.Endpoint = endpoint
|
||||
c.Soap.APIs["api2"] = api2Config
|
||||
}
|
||||
}
|
||||
// Otros parámetros pueden ser añadidos según sea necesario
|
||||
}
|
75
api-soap-facturacion/internal/database/model.go
Normal file
75
api-soap-facturacion/internal/database/model.go
Normal file
@ -0,0 +1,75 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Parameter representa un parámetro en la base de datos
|
||||
type Parameter struct {
|
||||
ID int `db:"id"`
|
||||
Key string `db:"key"`
|
||||
Value string `db:"value"`
|
||||
Description string `db:"description"`
|
||||
Active bool `db:"active"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// Estructura para las solicitudes SOAP
|
||||
type SoapRequest struct {
|
||||
ID int `db:"id"`
|
||||
RequestID string `db:"request_id"`
|
||||
APIID string `db:"api_id"`
|
||||
OperationName string `db:"operation_name"`
|
||||
RequestBody string `db:"request_body"`
|
||||
ResponseBody string `db:"response_body"`
|
||||
StatusCode int `db:"status_code"`
|
||||
Error string `db:"error"`
|
||||
RequestTime time.Time `db:"request_time"`
|
||||
ResponseTime time.Time `db:"response_time"`
|
||||
ProcessingTime int64 `db:"processing_time"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
// Inicialización de la base de datos: script SQL
|
||||
const DatabaseInitSQL = `
|
||||
-- Tabla de parámetros
|
||||
CREATE TABLE IF NOT EXISTS parameters (
|
||||
id SERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla para registro de solicitudes SOAP
|
||||
CREATE TABLE IF NOT EXISTS soap_requests (
|
||||
id SERIAL PRIMARY KEY,
|
||||
request_id VARCHAR(36) NOT NULL,
|
||||
api_id VARCHAR(50) NOT NULL,
|
||||
operation_name VARCHAR(100) NOT NULL,
|
||||
request_body TEXT NOT NULL,
|
||||
response_body TEXT,
|
||||
status_code INT,
|
||||
error TEXT,
|
||||
request_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
response_time TIMESTAMP WITH TIME ZONE,
|
||||
processing_time BIGINT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Índices
|
||||
CREATE INDEX IF NOT EXISTS idx_soap_requests_request_id ON soap_requests(request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_soap_requests_api_id ON soap_requests(api_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_soap_requests_operation_name ON soap_requests(operation_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_soap_requests_request_time ON soap_requests(request_time);
|
||||
|
||||
-- Insertar parámetros iniciales
|
||||
INSERT INTO parameters (key, value, description)
|
||||
VALUES
|
||||
('soap.api1.endpoint', 'https://api1.example.com/soap', 'Endpoint for API 1'),
|
||||
('soap.api2.endpoint', 'https://api2.example.com/soap', 'Endpoint for API 2')
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
`
|
49
api-soap-facturacion/internal/database/postgres.go
Normal file
49
api-soap-facturacion/internal/database/postgres.go
Normal file
@ -0,0 +1,49 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"api-soap-facturacion/internal/config"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// NewPostgresConnection crea una nueva conexión a PostgreSQL
|
||||
func NewPostgresConnection(cfg config.DatabaseConfig) (*sqlx.DB, error) {
|
||||
dsn := fmt.Sprintf(
|
||||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
|
||||
cfg.Host, cfg.Port, cfg.Username, cfg.Password, cfg.Database,
|
||||
)
|
||||
|
||||
db, err := sqlx.Connect("pgx", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configurar pool de conexiones
|
||||
db.SetMaxOpenConns(cfg.MaxOpenConns)
|
||||
db.SetMaxIdleConns(cfg.MaxIdleConns)
|
||||
db.SetConnMaxLifetime(cfg.ConnMaxLifetime * time.Hour)
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// LoadParameters carga parámetros desde la base de datos
|
||||
func LoadParameters(db *sqlx.DB) (map[string]string, error) {
|
||||
var params []Parameter
|
||||
query := `SELECT id, key, value, description, created_at, updated_at FROM parameters WHERE active = true`
|
||||
|
||||
err := db.Select(¶ms, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convertir a map
|
||||
result := make(map[string]string)
|
||||
for _, param := range params {
|
||||
result[param.Key] = param.Value
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
1
api-soap-facturacion/internal/logger/custom_encoder.go
Normal file
1
api-soap-facturacion/internal/logger/custom_encoder.go
Normal file
@ -0,0 +1 @@
|
||||
package logger
|
874
api-soap-facturacion/internal/logger/logger.go
Normal file
874
api-soap-facturacion/internal/logger/logger.go
Normal file
@ -0,0 +1,874 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"api-soap-facturacion/internal/config"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/buffer"
|
||||
"go.uber.org/zap/zapcore"
|
||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Logger es una interfaz que define los métodos necesarios para el logging
|
||||
type Logger interface {
|
||||
Debug(msg string, fields ...Field)
|
||||
Info(msg string, fields ...Field)
|
||||
Warn(msg string, fields ...Field)
|
||||
Error(msg string, fields ...Field)
|
||||
Fatal(msg string, fields ...Field)
|
||||
With(fields ...Field) Logger
|
||||
}
|
||||
|
||||
// Field es un campo para agregar contexto a los logs
|
||||
type Field struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// zapLogger implementa la interfaz Logger usando zap
|
||||
type zapLogger struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// CustomEncoder implementa la interfaz zapcore.Encoder
|
||||
type CustomEncoder struct {
|
||||
zapcore.Encoder
|
||||
pool buffer.Pool
|
||||
serviceName string
|
||||
}
|
||||
|
||||
// NewCustomEncoder crea un nuevo encoder personalizado
|
||||
func NewCustomEncoder(cfg zapcore.EncoderConfig, serviceName string) zapcore.Encoder {
|
||||
return &CustomEncoder{
|
||||
Encoder: zapcore.NewJSONEncoder(cfg),
|
||||
pool: buffer.NewPool(),
|
||||
serviceName: serviceName,
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeEntry implementa el formato personalizado
|
||||
// EncodeEntry implementa el formato personalizado
|
||||
func (e *CustomEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
|
||||
line := e.pool.Get()
|
||||
|
||||
// Agregar nivel (INFO:, WARN:, etc.)
|
||||
levelStr := entry.Level.CapitalString()
|
||||
line.AppendString(levelStr)
|
||||
line.AppendString(": ")
|
||||
|
||||
// Agregar timestamp
|
||||
timestamp := entry.Time.Format("2006/01/02 15:04:05")
|
||||
line.AppendString(timestamp)
|
||||
line.AppendString(" ")
|
||||
|
||||
// Agregar información del archivo/línea si está disponible
|
||||
if entry.Caller.Defined {
|
||||
// Convertir la ruta a formato Unix para manejar uniformemente las barras
|
||||
filePath := strings.ReplaceAll(entry.Caller.File, "\\", "/")
|
||||
|
||||
// Opciones para encontrar la parte relevante del path
|
||||
possiblePrefixes := []string{
|
||||
"/cmd/",
|
||||
"/internal/",
|
||||
"/pkg/",
|
||||
"/api/",
|
||||
"/app/",
|
||||
"/src/",
|
||||
}
|
||||
|
||||
// Buscar el prefijo más cercano al final del path
|
||||
shortPath := filePath
|
||||
for _, prefix := range possiblePrefixes {
|
||||
if idx := strings.LastIndex(filePath, prefix); idx != -1 {
|
||||
// Encontramos un prefijo estándar de Go
|
||||
shortPath = filePath[idx+1:] // +1 para quitar el slash inicial
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Si no se encontró ningún prefijo, usar el último componente del path
|
||||
if shortPath == filePath {
|
||||
parts := strings.Split(filePath, "/")
|
||||
if len(parts) > 0 {
|
||||
shortPath = parts[len(parts)-1]
|
||||
}
|
||||
}
|
||||
|
||||
caller := fmt.Sprintf("%s:%d: ", shortPath, entry.Caller.Line)
|
||||
line.AppendString(caller)
|
||||
}
|
||||
|
||||
// Agregar nombre del servicio entre corchetes
|
||||
line.AppendString("[")
|
||||
line.AppendString(e.serviceName)
|
||||
line.AppendString("] ")
|
||||
|
||||
// Agregar mensaje
|
||||
line.AppendString(entry.Message)
|
||||
|
||||
// Procesar campos adicionales
|
||||
if len(fields) > 0 {
|
||||
// Usar encoder JSON para serializar los campos
|
||||
encoder := zapcore.NewMapObjectEncoder()
|
||||
|
||||
// Agregar cada campo al encoder, excepto "service"
|
||||
for _, field := range fields {
|
||||
if field.Key != "service" {
|
||||
field.AddTo(encoder)
|
||||
}
|
||||
}
|
||||
|
||||
// Solo mostramos los campos si hay alguno después de filtrar
|
||||
if len(encoder.Fields) > 0 {
|
||||
line.AppendString(" ")
|
||||
fieldsJSON, err := json.Marshal(encoder.Fields)
|
||||
if err == nil {
|
||||
line.AppendString(string(fieldsJSON))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line.AppendString("\n")
|
||||
return line, nil
|
||||
}
|
||||
|
||||
//func (e *CustomEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
|
||||
// line := e.pool.Get()
|
||||
//
|
||||
// // Agregar nivel (INFO:, WARN:, etc.)
|
||||
// levelStr := entry.Level.CapitalString()
|
||||
// line.AppendString(levelStr)
|
||||
// line.AppendString(": ")
|
||||
//
|
||||
// // Agregar timestamp
|
||||
// timestamp := entry.Time.Format("2006/01/02 15:04:05")
|
||||
// line.AppendString(timestamp)
|
||||
// line.AppendString(" ")
|
||||
//
|
||||
// // Agregar información del archivo/línea si está disponible
|
||||
// if entry.Caller.Defined {
|
||||
// // Obtener solo la parte relevante de la ruta (después del último "api-soap-facturacion")
|
||||
// filePath := entry.Caller.File
|
||||
// if idx := strings.LastIndex(filePath, "api-soap-facturacion"); idx != -1 {
|
||||
// // Si encontramos la cadena, tomamos lo que viene después más "api-soap-facturacion"
|
||||
// filePath = filePath[idx:]
|
||||
// } else {
|
||||
// // Si no encontramos la cadena, intentamos tomar solo el nombre del archivo
|
||||
// if idx := strings.LastIndex(filePath, "/"); idx != -1 {
|
||||
// filePath = filePath[idx+1:]
|
||||
// } else if idx := strings.LastIndex(filePath, "\\"); idx != -1 {
|
||||
// filePath = filePath[idx+1:]
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// caller := fmt.Sprintf("%s:%d: ", filePath, entry.Caller.Line)
|
||||
// line.AppendString(caller)
|
||||
// }
|
||||
// // Agregar nombre del servicio entre corchetes
|
||||
// line.AppendString("[")
|
||||
// line.AppendString(e.serviceName)
|
||||
// line.AppendString("] ")
|
||||
//
|
||||
// // Agregar mensaje
|
||||
// line.AppendString(entry.Message)
|
||||
//
|
||||
// // Procesar campos adicionales
|
||||
// if len(fields) > 0 {
|
||||
// // Usar encoder JSON para serializar los campos
|
||||
// encoder := zapcore.NewMapObjectEncoder()
|
||||
//
|
||||
// // Agregar cada campo al encoder, excepto "service"
|
||||
// for _, field := range fields {
|
||||
// if field.Key != "service" {
|
||||
// field.AddTo(encoder)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Solo mostramos los campos si hay alguno después de filtrar
|
||||
// if len(encoder.Fields) > 0 {
|
||||
// line.AppendString(" ")
|
||||
// fieldsJSON, err := json.Marshal(encoder.Fields)
|
||||
// if err == nil {
|
||||
// line.AppendString(string(fieldsJSON))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// line.AppendString("\n")
|
||||
// return line, nil
|
||||
//}
|
||||
|
||||
//func (e *CustomEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
|
||||
// line := e.pool.Get()
|
||||
//
|
||||
// // Agregar nivel (INFO:, WARN:, etc.)
|
||||
// levelStr := entry.Level.CapitalString()
|
||||
// line.AppendString(levelStr)
|
||||
// line.AppendString(": ")
|
||||
//
|
||||
// // Agregar timestamp
|
||||
// timestamp := entry.Time.Format("2006/01/02 15:04:05")
|
||||
// line.AppendString(timestamp)
|
||||
// line.AppendString(" ")
|
||||
//
|
||||
// // Agregar información del archivo/línea si está disponible
|
||||
// if entry.Caller.Defined {
|
||||
// caller := fmt.Sprintf("%s:%d: ", entry.Caller.File, entry.Caller.Line)
|
||||
// line.AppendString(caller)
|
||||
// }
|
||||
//
|
||||
// // Agregar nombre del servicio entre corchetes
|
||||
// line.AppendString("[")
|
||||
// line.AppendString(e.serviceName)
|
||||
// line.AppendString("] ")
|
||||
//
|
||||
// // Agregar mensaje
|
||||
// line.AppendString(entry.Message)
|
||||
//
|
||||
// // Procesar campos adicionales
|
||||
// if len(fields) > 0 {
|
||||
// // Usar encoder JSON para serializar los campos
|
||||
// encoder := zapcore.NewMapObjectEncoder()
|
||||
//
|
||||
// // Agregar cada campo al encoder, excepto "service"
|
||||
// for _, field := range fields {
|
||||
// if field.Key != "service" {
|
||||
// field.AddTo(encoder)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Solo mostramos los campos si hay alguno después de filtrar
|
||||
// if len(encoder.Fields) > 0 {
|
||||
// line.AppendString(" ")
|
||||
// fieldsJSON, err := json.Marshal(encoder.Fields)
|
||||
// if err == nil {
|
||||
// line.AppendString(string(fieldsJSON))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// line.AppendString("\n")
|
||||
// return line, nil
|
||||
//}
|
||||
|
||||
// Clone implementa la interfaz Encoder
|
||||
func (e *CustomEncoder) Clone() zapcore.Encoder {
|
||||
return &CustomEncoder{
|
||||
Encoder: e.Encoder.Clone(),
|
||||
pool: e.pool,
|
||||
serviceName: e.serviceName,
|
||||
}
|
||||
}
|
||||
|
||||
// NewLogger crea una nueva instancia de Logger
|
||||
func NewLogger(cfg *config.Config) Logger {
|
||||
// Configurar nivel de log
|
||||
var level zapcore.Level
|
||||
switch cfg.App.LogLevel {
|
||||
case "debug":
|
||||
level = zapcore.DebugLevel
|
||||
case "info":
|
||||
level = zapcore.InfoLevel
|
||||
case "warn":
|
||||
level = zapcore.WarnLevel
|
||||
case "error":
|
||||
level = zapcore.ErrorLevel
|
||||
default:
|
||||
level = zapcore.InfoLevel
|
||||
}
|
||||
|
||||
// Configuración básica para el encoder
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
FunctionKey: zapcore.OmitKey,
|
||||
MessageKey: "msg",
|
||||
StacktraceKey: "stacktrace",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.CapitalLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
|
||||
// Crear el encoder personalizado
|
||||
customEncoder := NewCustomEncoder(encoderConfig, cfg.App.Name)
|
||||
|
||||
// Configurar el output
|
||||
var core zapcore.Core
|
||||
if cfg.App.LogFilePath != "" {
|
||||
// Si se usa archivo con lumberjack
|
||||
logWriter := &lumberjack.Logger{
|
||||
Filename: cfg.App.LogFilePath,
|
||||
MaxSize: cfg.App.LogMaxSize,
|
||||
MaxBackups: cfg.App.LogMaxBackups,
|
||||
MaxAge: cfg.App.LogMaxAge,
|
||||
Compress: cfg.App.LogCompress,
|
||||
}
|
||||
|
||||
core = zapcore.NewCore(
|
||||
customEncoder,
|
||||
zapcore.AddSync(logWriter),
|
||||
level,
|
||||
)
|
||||
} else {
|
||||
// Solo usar stdout
|
||||
core = zapcore.NewCore(
|
||||
customEncoder,
|
||||
zapcore.AddSync(os.Stdout),
|
||||
level,
|
||||
)
|
||||
}
|
||||
|
||||
// Habilitar el caller para mostrar información del archivo y línea
|
||||
zapLog := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
|
||||
|
||||
// Ya no agregamos el nombre del servicio como campo, ya que lo incluimos en el formato
|
||||
// zapLog = zapLog.With(zap.String("service", cfg.App.Name))
|
||||
|
||||
return &zapLogger{logger: zapLog}
|
||||
}
|
||||
|
||||
// NewField crea un campo para agregar a los logs
|
||||
func NewField(key string, value interface{}) Field {
|
||||
return Field{Key: key, Value: value}
|
||||
}
|
||||
|
||||
// convertFields convierte nuestros campos a campos de zap
|
||||
func (l *zapLogger) convertFields(fields []Field) []zap.Field {
|
||||
zapFields := make([]zap.Field, len(fields))
|
||||
for i, field := range fields {
|
||||
zapFields[i] = zap.Any(field.Key, field.Value)
|
||||
}
|
||||
return zapFields
|
||||
}
|
||||
|
||||
// Debug registra un mensaje con nivel Debug
|
||||
func (l *zapLogger) Debug(msg string, fields ...Field) {
|
||||
l.logger.Debug(msg, l.convertFields(fields)...)
|
||||
}
|
||||
|
||||
// Info registra un mensaje con nivel Info
|
||||
func (l *zapLogger) Info(msg string, fields ...Field) {
|
||||
l.logger.Info(msg, l.convertFields(fields)...)
|
||||
}
|
||||
|
||||
// Warn registra un mensaje con nivel Warn
|
||||
func (l *zapLogger) Warn(msg string, fields ...Field) {
|
||||
l.logger.Warn(msg, l.convertFields(fields)...)
|
||||
}
|
||||
|
||||
// Error registra un mensaje con nivel Error
|
||||
func (l *zapLogger) Error(msg string, fields ...Field) {
|
||||
l.logger.Error(msg, l.convertFields(fields)...)
|
||||
}
|
||||
|
||||
// Fatal registra un mensaje con nivel Fatal
|
||||
func (l *zapLogger) Fatal(msg string, fields ...Field) {
|
||||
l.logger.Fatal(msg, l.convertFields(fields)...)
|
||||
}
|
||||
|
||||
// With retorna un nuevo logger con campos adicionales
|
||||
func (l *zapLogger) With(fields ...Field) Logger {
|
||||
return &zapLogger{
|
||||
logger: l.logger.With(l.convertFields(fields)...),
|
||||
}
|
||||
}
|
||||
|
||||
//package logger
|
||||
//
|
||||
//import (
|
||||
// "api-soap-facturacion/internal/config"
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "go.uber.org/zap"
|
||||
// "go.uber.org/zap/zapcore"
|
||||
// lumberjack "gopkg.in/natefinch/lumberjack.v2" // Usa este alias
|
||||
// "os"
|
||||
//)
|
||||
//
|
||||
//// Logger es una interfaz que define los métodos necesarios para el logging
|
||||
//type Logger interface {
|
||||
// Debug(msg string, fields ...Field)
|
||||
// Info(msg string, fields ...Field)
|
||||
// Warn(msg string, fields ...Field)
|
||||
// Error(msg string, fields ...Field)
|
||||
// Fatal(msg string, fields ...Field)
|
||||
// With(fields ...Field) Logger
|
||||
//}
|
||||
//
|
||||
//// Field es un campo para agregar contexto a los logs
|
||||
//type Field struct {
|
||||
// Key string
|
||||
// Value interface{}
|
||||
//}
|
||||
//
|
||||
//// zapLogger implementa la interfaz Logger usando zap
|
||||
//type zapLogger struct {
|
||||
// logger *zap.Logger
|
||||
//}
|
||||
//
|
||||
//// CustomEncoder implementa la interfaz zapcore.Encoder
|
||||
//type CustomEncoder struct {
|
||||
// zapcore.Encoder
|
||||
// pool buffer.Pool
|
||||
// serviceName string
|
||||
//}
|
||||
//
|
||||
//// NewCustomEncoder crea un nuevo encoder personalizado
|
||||
//func NewCustomEncoder(cfg zapcore.EncoderConfig, serviceName string) zapcore.Encoder {
|
||||
// return &CustomEncoder{
|
||||
// Encoder: zapcore.NewJSONEncoder(cfg),
|
||||
// pool: buffer.NewPool(),
|
||||
// serviceName: serviceName,
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// EncodeEntry implementa el formato personalizado
|
||||
//func (e *CustomEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
|
||||
// line := e.pool.Get()
|
||||
//
|
||||
// // Agregar nivel (INFO:, WARN:, etc.)
|
||||
// levelStr := entry.Level.CapitalString()
|
||||
// line.AppendString(levelStr)
|
||||
// line.AppendString(": ")
|
||||
//
|
||||
// // Agregar timestamp
|
||||
// timestamp := entry.Time.Format("2006/01/02 15:04:05")
|
||||
// line.AppendString(timestamp)
|
||||
// line.AppendString(" ")
|
||||
//
|
||||
// // Agregar información del archivo/línea si está disponible
|
||||
// if entry.Caller.Defined {
|
||||
// caller := fmt.Sprintf("%s:%d: ", entry.Caller.File, entry.Caller.Line)
|
||||
// line.AppendString(caller)
|
||||
// }
|
||||
//
|
||||
// // Agregar nombre del servicio entre corchetes
|
||||
// line.AppendString("[")
|
||||
// line.AppendString(e.serviceName)
|
||||
// line.AppendString("] ")
|
||||
//
|
||||
// // Agregar mensaje
|
||||
// line.AppendString(entry.Message)
|
||||
//
|
||||
// // Procesar campos adicionales
|
||||
// if len(fields) > 0 {
|
||||
// line.AppendString(" ")
|
||||
// fieldsMap := make(map[string]interface{})
|
||||
// for _, field := range fields {
|
||||
// if field.Key != "service" {
|
||||
// field.AddTo(fieldsMap)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if len(fieldsMap) > 0 {
|
||||
// fieldJSON, err := json.Marshal(fieldsMap)
|
||||
// if err == nil {
|
||||
// line.AppendString(string(fieldJSON))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// line.AppendString("\n")
|
||||
// return line, nil
|
||||
//}
|
||||
//
|
||||
//// Clone implementa la interfaz Encoder
|
||||
//func (e *CustomEncoder) Clone() zapcore.Encoder {
|
||||
// return &CustomEncoder{
|
||||
// Encoder: e.Encoder.Clone(),
|
||||
// pool: e.pool,
|
||||
// serviceName: e.serviceName,
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// NewLogger crea una nueva instancia de Logger tipo json
|
||||
////func NewLogger(cfg *config.Config) Logger {
|
||||
//// // Configurar nivel de log
|
||||
//// var level zapcore.Level
|
||||
//// switch cfg.App.LogLevel {
|
||||
//// case "debug":
|
||||
//// level = zapcore.DebugLevel
|
||||
//// case "info":
|
||||
//// level = zapcore.InfoLevel
|
||||
//// case "warn":
|
||||
//// level = zapcore.WarnLevel
|
||||
//// case "error":
|
||||
//// level = zapcore.ErrorLevel
|
||||
//// default:
|
||||
//// level = zapcore.InfoLevel
|
||||
//// }
|
||||
////
|
||||
//// // Configurar encoderConfig
|
||||
//// encoderConfig := zapcore.EncoderConfig{
|
||||
//// TimeKey: "time",
|
||||
//// LevelKey: "level",
|
||||
//// NameKey: "logger",
|
||||
//// CallerKey: "caller",
|
||||
//// MessageKey: "msg",
|
||||
//// StacktraceKey: "stacktrace",
|
||||
//// LineEnding: zapcore.DefaultLineEnding,
|
||||
//// EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
//// EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
//// EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
//// EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
//// }
|
||||
////
|
||||
//// // Crear escritores
|
||||
//// var cores []zapcore.Core
|
||||
////
|
||||
//// // Siempre escribir a stdout
|
||||
//// cores = append(cores, zapcore.NewCore(
|
||||
//// zapcore.NewJSONEncoder(encoderConfig),
|
||||
//// zapcore.AddSync(os.Stdout),
|
||||
//// level,
|
||||
//// ))
|
||||
////
|
||||
//// // Si se especificó una ruta de archivo de logs, configurar rotación
|
||||
//// if cfg.App.LogFilePath != "" {
|
||||
//// logWriter := &lumberjack.Logger{
|
||||
//// Filename: cfg.App.LogFilePath,
|
||||
//// MaxSize: cfg.App.LogMaxSize,
|
||||
//// MaxBackups: cfg.App.LogMaxBackups,
|
||||
//// MaxAge: cfg.App.LogMaxAge,
|
||||
//// Compress: cfg.App.LogCompress,
|
||||
//// }
|
||||
////
|
||||
//// cores = append(cores, zapcore.NewCore(
|
||||
//// zapcore.NewJSONEncoder(encoderConfig),
|
||||
//// zapcore.AddSync(logWriter),
|
||||
//// level,
|
||||
//// ))
|
||||
//// }
|
||||
////
|
||||
//// // Combinar cores
|
||||
//// core := zapcore.NewTee(cores...)
|
||||
////
|
||||
//// // Crear logger
|
||||
//// zapLog := zap.New(core)
|
||||
//// zapLog = zapLog.With(zap.String("service", cfg.App.Name))
|
||||
////
|
||||
//// return &zapLogger{logger: zapLog}
|
||||
////}
|
||||
//
|
||||
//// fomato de texto mas flexible
|
||||
//// NewLogger crea una nueva instancia de Logger
|
||||
////func NewLogger(cfg *config.Config) Logger {
|
||||
//// // Configurar nivel de log
|
||||
//// var level zapcore.Level
|
||||
//// switch cfg.App.LogLevel {
|
||||
//// case "debug":
|
||||
//// level = zapcore.DebugLevel
|
||||
//// case "info":
|
||||
//// level = zapcore.InfoLevel
|
||||
//// case "warn":
|
||||
//// level = zapcore.WarnLevel
|
||||
//// case "error":
|
||||
//// level = zapcore.ErrorLevel
|
||||
//// default:
|
||||
//// level = zapcore.InfoLevel
|
||||
//// }
|
||||
////
|
||||
//// // Configurar encoderConfig para un formato más legible
|
||||
//// encoderConfig := zapcore.EncoderConfig{
|
||||
//// TimeKey: "time",
|
||||
//// LevelKey: "level",
|
||||
//// NameKey: "logger",
|
||||
//// CallerKey: "caller",
|
||||
//// FunctionKey: zapcore.OmitKey,
|
||||
//// MessageKey: "msg",
|
||||
//// StacktraceKey: "stacktrace",
|
||||
//// LineEnding: zapcore.DefaultLineEnding,
|
||||
//// EncodeLevel: zapcore.CapitalLevelEncoder, // INFO en lugar de info
|
||||
//// EncodeTime: zapcore.TimeEncoderOfLayout("2006/01/02 15:04:05"), // Formato como 2025/05/15 00:13:07
|
||||
//// EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
//// EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
//// }
|
||||
////
|
||||
//// // Crear un formato personalizado
|
||||
//// consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
|
||||
////
|
||||
//// // Configurar el output final
|
||||
//// var core zapcore.Core
|
||||
//// if cfg.App.LogFilePath != "" && cfg.App.UseFileLogger {
|
||||
//// // Si se usa archivo con lumberjack
|
||||
//// logWriter := &lumberjack.Logger{
|
||||
//// Filename: cfg.App.LogFilePath,
|
||||
//// MaxSize: cfg.App.LogMaxSize,
|
||||
//// MaxBackups: cfg.App.LogMaxBackups,
|
||||
//// MaxAge: cfg.App.LogMaxAge,
|
||||
//// Compress: cfg.App.LogCompress,
|
||||
//// }
|
||||
////
|
||||
//// core = zapcore.NewCore(
|
||||
//// consoleEncoder,
|
||||
//// zapcore.AddSync(logWriter),
|
||||
//// level,
|
||||
//// )
|
||||
//// } else {
|
||||
//// // Solo usar stdout
|
||||
//// core = zapcore.NewCore(
|
||||
//// consoleEncoder,
|
||||
//// zapcore.AddSync(os.Stdout),
|
||||
//// level,
|
||||
//// )
|
||||
//// }
|
||||
////
|
||||
//// // Habilitar el caller para mostrar información del archivo y línea
|
||||
//// zapLog := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
|
||||
////
|
||||
//// // Agregar nombre del servicio como prefijo constante
|
||||
//// zapLog = zapLog.With(zap.String("service", cfg.App.Name))
|
||||
////
|
||||
//// return &zapLogger{logger: zapLog}
|
||||
////}
|
||||
//
|
||||
//// NewLogger crea una nueva instancia de Logger
|
||||
//func NewLogger(cfg *config.Config) Logger {
|
||||
// // Configurar nivel de log
|
||||
// var level zapcore.Level
|
||||
// switch cfg.App.LogLevel {
|
||||
// case "debug":
|
||||
// level = zapcore.DebugLevel
|
||||
// case "info":
|
||||
// level = zapcore.InfoLevel
|
||||
// case "warn":
|
||||
// level = zapcore.WarnLevel
|
||||
// case "error":
|
||||
// level = zapcore.ErrorLevel
|
||||
// default:
|
||||
// level = zapcore.InfoLevel
|
||||
// }
|
||||
//
|
||||
// // Configuración básica para el encoder
|
||||
// encoderConfig := zapcore.EncoderConfig{
|
||||
// TimeKey: "time",
|
||||
// LevelKey: "level",
|
||||
// NameKey: "logger",
|
||||
// CallerKey: "caller",
|
||||
// FunctionKey: zapcore.OmitKey,
|
||||
// MessageKey: "msg",
|
||||
// StacktraceKey: "stacktrace",
|
||||
// LineEnding: zapcore.DefaultLineEnding,
|
||||
// EncodeLevel: zapcore.CapitalLevelEncoder,
|
||||
// EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
// EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
// EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
// }
|
||||
//
|
||||
// // Crear el encoder personalizado
|
||||
// customEncoder := NewCustomEncoder(encoderConfig, cfg.App.Name)
|
||||
//
|
||||
// // Configurar el output
|
||||
// var core zapcore.Core
|
||||
// if cfg.App.LogFilePath != "" {
|
||||
// // Si se usa archivo con lumberjack
|
||||
// logWriter := &lumberjack.Logger{
|
||||
// Filename: cfg.App.LogFilePath,
|
||||
// MaxSize: cfg.App.LogMaxSize,
|
||||
// MaxBackups: cfg.App.LogMaxBackups,
|
||||
// MaxAge: cfg.App.LogMaxAge,
|
||||
// Compress: cfg.App.LogCompress,
|
||||
// }
|
||||
//
|
||||
// core = zapcore.NewCore(
|
||||
// customEncoder,
|
||||
// zapcore.AddSync(logWriter),
|
||||
// level,
|
||||
// )
|
||||
// } else {
|
||||
// // Solo usar stdout
|
||||
// core = zapcore.NewCore(
|
||||
// customEncoder,
|
||||
// zapcore.AddSync(os.Stdout),
|
||||
// level,
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // Habilitar el caller para mostrar información del archivo y línea
|
||||
// zapLog := zap.New(core, zap.AddCaller())
|
||||
//
|
||||
// // Ya no agregamos el nombre del servicio como campo, ya que lo incluimos en el formato
|
||||
// // zapLog = zapLog.With(zap.String("service", cfg.App.Name))
|
||||
//
|
||||
// return &zapLogger{logger: zapLog}
|
||||
//}
|
||||
//
|
||||
//// NewField crea un campo para agregar a los logs
|
||||
//func NewField(key string, value interface{}) Field {
|
||||
// return Field{Key: key, Value: value}
|
||||
//}
|
||||
//
|
||||
//// convertFields convierte nuestros campos a campos de zap
|
||||
//func (l *zapLogger) convertFields(fields []Field) []zap.Field {
|
||||
// zapFields := make([]zap.Field, len(fields))
|
||||
// for i, field := range fields {
|
||||
// zapFields[i] = zap.Any(field.Key, field.Value)
|
||||
// }
|
||||
// return zapFields
|
||||
//}
|
||||
//
|
||||
//// Debug registra un mensaje con nivel Debug
|
||||
//func (l *zapLogger) Debug(msg string, fields ...Field) {
|
||||
// l.logger.Debug(msg, l.convertFields(fields)...)
|
||||
//}
|
||||
//
|
||||
//// Info registra un mensaje con nivel Info
|
||||
//func (l *zapLogger) Info(msg string, fields ...Field) {
|
||||
// l.logger.Info(msg, l.convertFields(fields)...)
|
||||
//}
|
||||
//
|
||||
//// Warn registra un mensaje con nivel Warn
|
||||
//func (l *zapLogger) Warn(msg string, fields ...Field) {
|
||||
// l.logger.Warn(msg, l.convertFields(fields)...)
|
||||
//}
|
||||
//
|
||||
//// Error registra un mensaje con nivel Error
|
||||
//func (l *zapLogger) Error(msg string, fields ...Field) {
|
||||
// l.logger.Error(msg, l.convertFields(fields)...)
|
||||
//}
|
||||
//
|
||||
//// Fatal registra un mensaje con nivel Fatal
|
||||
//func (l *zapLogger) Fatal(msg string, fields ...Field) {
|
||||
// l.logger.Fatal(msg, l.convertFields(fields)...)
|
||||
//}
|
||||
//
|
||||
//// With retorna un nuevo logger con campos adicionales
|
||||
////
|
||||
//// func (l *zapLogger) With(fields ...Field) Logger {
|
||||
//// return &zapLogger{
|
||||
//// logger: l.logger.With(l.convertFields(fields)...),
|
||||
//// }
|
||||
//// }
|
||||
//func (l *zapLogger) With(fields ...Field) Logger {
|
||||
// return &zapLogger{
|
||||
// logger: l.logger.With(l.convertFields(fields)...),
|
||||
// }
|
||||
//}
|
||||
//
|
||||
////package logger
|
||||
////
|
||||
////import (
|
||||
//// "api-soap-facturacion/internal/config"
|
||||
//// "go.uber.org/zap"
|
||||
//// "go.uber.org/zap/zapcore"
|
||||
////)
|
||||
////
|
||||
////// Logger es una interfaz que define los métodos necesarios para el logging
|
||||
////type Logger interface {
|
||||
//// Debug(msg string, fields ...Field)
|
||||
//// Info(msg string, fields ...Field)
|
||||
//// Warn(msg string, fields ...Field)
|
||||
//// Error(msg string, fields ...Field)
|
||||
//// Fatal(msg string, fields ...Field)
|
||||
//// With(fields ...Field) Logger
|
||||
////}
|
||||
////
|
||||
////// Field es un campo para agregar contexto a los logs
|
||||
////type Field struct {
|
||||
//// Key string
|
||||
//// Value interface{}
|
||||
////}
|
||||
////
|
||||
////// zapLogger implementa la interfaz Logger usando zap
|
||||
////type zapLogger struct {
|
||||
//// logger *zap.Logger
|
||||
////}
|
||||
////
|
||||
////// NewLogger crea una nueva instancia de Logger
|
||||
////func NewLogger(cfg *config.Config) Logger {
|
||||
//// // Configurar nivel de log
|
||||
//// var level zapcore.Level
|
||||
//// switch cfg.App.LogLevel {
|
||||
//// case "debug":
|
||||
//// level = zapcore.DebugLevel
|
||||
//// case "info":
|
||||
//// level = zapcore.InfoLevel
|
||||
//// case "warn":
|
||||
//// level = zapcore.WarnLevel
|
||||
//// case "error":
|
||||
//// level = zapcore.ErrorLevel
|
||||
//// default:
|
||||
//// level = zapcore.InfoLevel
|
||||
//// }
|
||||
////
|
||||
//// // Configuración de zap
|
||||
//// zapConfig := zap.Config{
|
||||
//// Level: zap.NewAtomicLevelAt(level),
|
||||
//// Development: cfg.App.Environment == "development",
|
||||
//// Encoding: "json",
|
||||
//// OutputPaths: []string{"stdout"},
|
||||
//// ErrorOutputPaths: []string{"stderr"},
|
||||
//// EncoderConfig: zapcore.EncoderConfig{
|
||||
//// TimeKey: "time",
|
||||
//// LevelKey: "level",
|
||||
//// NameKey: "logger",
|
||||
//// CallerKey: "caller",
|
||||
//// MessageKey: "msg",
|
||||
//// StacktraceKey: "stacktrace",
|
||||
//// LineEnding: zapcore.DefaultLineEnding,
|
||||
//// EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
//// EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
//// EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
//// EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
//// },
|
||||
//// }
|
||||
////
|
||||
//// // Agregar service name a todos los logs
|
||||
//// logger, _ := zapConfig.Build()
|
||||
//// logger = logger.With(zap.String("service", cfg.App.Name))
|
||||
////
|
||||
//// return &zapLogger{logger: logger}
|
||||
////}
|
||||
////
|
||||
////// NewField crea un campo para agregar a los logs
|
||||
////func NewField(key string, value interface{}) Field {
|
||||
//// return Field{Key: key, Value: value}
|
||||
////}
|
||||
////
|
||||
////// convertFields convierte nuestros campos a campos de zap
|
||||
////func (l *zapLogger) convertFields(fields []Field) []zap.Field {
|
||||
//// zapFields := make([]zap.Field, len(fields))
|
||||
//// for i, field := range fields {
|
||||
//// zapFields[i] = zap.Any(field.Key, field.Value)
|
||||
//// }
|
||||
//// return zapFields
|
||||
////}
|
||||
////
|
||||
////// Debug registra un mensaje con nivel Debug
|
||||
////func (l *zapLogger) Debug(msg string, fields ...Field) {
|
||||
//// l.logger.Debug(msg, l.convertFields(fields)...)
|
||||
////}
|
||||
////
|
||||
////// Info registra un mensaje con nivel Info
|
||||
////func (l *zapLogger) Info(msg string, fields ...Field) {
|
||||
//// l.logger.Info(msg, l.convertFields(fields)...)
|
||||
////}
|
||||
////
|
||||
////// Warn registra un mensaje con nivel Warn
|
||||
////func (l *zapLogger) Warn(msg string, fields ...Field) {
|
||||
//// l.logger.Warn(msg, l.convertFields(fields)...)
|
||||
////}
|
||||
////
|
||||
////// Error registra un mensaje con nivel Error
|
||||
////func (l *zapLogger) Error(msg string, fields ...Field) {
|
||||
//// l.logger.Error(msg, l.convertFields(fields)...)
|
||||
////}
|
||||
////
|
||||
////// Fatal registra un mensaje con nivel Fatal
|
||||
////func (l *zapLogger) Fatal(msg string, fields ...Field) {
|
||||
//// l.logger.Fatal(msg, l.convertFields(fields)...)
|
||||
////}
|
||||
////
|
||||
////// With retorna un nuevo logger con campos adicionales
|
||||
////func (l *zapLogger) With(fields ...Field) Logger {
|
||||
//// return &zapLogger{
|
||||
//// logger: l.logger.With(l.convertFields(fields)...),
|
||||
//// }
|
||||
////}
|
@ -0,0 +1,51 @@
|
||||
package facturacionOperaciones
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// SolicitudRegistroPuntoVenta representa los datos necesarios para registrar un punto de venta
|
||||
type SolicitudRegistroPuntoVenta struct {
|
||||
CodigoAmbiente string `xml:"codigoAmbiente"`
|
||||
CodigoModalidad string `xml:"codigoModalidad"`
|
||||
CodigoSistema string `xml:"codigoSistema"`
|
||||
CodigoSucursal string `xml:"codigoSucursal"`
|
||||
CodigoTipoPuntoVenta string `xml:"codigoTipoPuntoVenta"`
|
||||
CUIS string `xml:"cuis"`
|
||||
Descripcion string `xml:"descripcion"`
|
||||
NIT string `xml:"nit"`
|
||||
NombrePuntoVenta string `xml:"nombrePuntoVenta"`
|
||||
}
|
||||
|
||||
// RegistroPuntoVentaRequest es la estructura de petición SOAP
|
||||
type RegistroPuntoVentaRequest struct {
|
||||
XMLName xml.Name `xml:"soapenv:Envelope"`
|
||||
SoapEnv string `xml:"xmlns:soapenv,attr"`
|
||||
Siat string `xml:"xmlns:siat,attr"`
|
||||
Body struct {
|
||||
RegistroPuntoVenta struct {
|
||||
Solicitud SolicitudRegistroPuntoVenta
|
||||
} `xml:"siat:registroPuntoVenta"`
|
||||
} `xml:"soapenv:Body"`
|
||||
}
|
||||
|
||||
// Mensaje representa un mensaje de la respuesta SOAP
|
||||
type Mensaje struct {
|
||||
Codigo string `xml:"codigo"`
|
||||
Descripcion string `xml:"descripcion"`
|
||||
}
|
||||
|
||||
// RespuestaRegistroPuntoVenta representa la respuesta del servicio SOAP
|
||||
type RespuestaRegistroPuntoVenta struct {
|
||||
CodigoPuntoVenta string `xml:"codigoPuntoVenta"`
|
||||
MensajesList []Mensaje `xml:"mensajesList"`
|
||||
Transaccion string `xml:"transaccion"`
|
||||
}
|
||||
|
||||
// RegistroPuntoVentaResponse es la estructura de respuesta SOAP
|
||||
type RegistroPuntoVentaResponse struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body struct {
|
||||
RegistroPuntoVentaResponse struct {
|
||||
Respuesta RespuestaRegistroPuntoVenta `xml:"RespuestaRegistroPuntoVenta"`
|
||||
} `xml:"registroPuntoVentaResponse"`
|
||||
} `xml:"Body"`
|
||||
}
|
93
api-soap-facturacion/internal/soap/api/client.go
Normal file
93
api-soap-facturacion/internal/soap/api/client.go
Normal file
@ -0,0 +1,93 @@
|
||||
package api1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"api-soap-facturacion/internal/config"
|
||||
"api-soap-facturacion/internal/logger"
|
||||
"api-soap-facturacion/pkg/errors"
|
||||
"api-soap-facturacion/pkg/soap"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Client es la interfaz para el cliente de API1
|
||||
type Client interface {
|
||||
GetData(ctx context.Context, request GetDataRequest) (*GetDataResponse, error)
|
||||
// Agregar más métodos según sea necesario
|
||||
}
|
||||
|
||||
// client implementa la interfaz Client
|
||||
type client struct {
|
||||
soapClient soap.Client
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewClient crea un nuevo cliente para API1
|
||||
func NewClient(config config.APIConfig, log logger.Logger) Client {
|
||||
// Configurar cliente SOAP
|
||||
options := soap.ClientOptions{
|
||||
Endpoint: config.Endpoint,
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
Timeout: config.Timeout * time.Second,
|
||||
Headers: map[string]string{
|
||||
"User-Agent": "soap-api-client/1.0",
|
||||
},
|
||||
}
|
||||
|
||||
return &client{
|
||||
soapClient: soap.NewClient(options),
|
||||
logger: log,
|
||||
}
|
||||
}
|
||||
|
||||
// GetData realiza una solicitud GetData a API1
|
||||
func (c *client) GetData(ctx context.Context, request GetDataRequest) (*GetDataResponse, error) {
|
||||
// Generar ID de solicitud si no existe
|
||||
var requestID string
|
||||
if id, ok := ctx.Value(soap.RequestIDKey{}).(string); ok {
|
||||
requestID = id
|
||||
} else {
|
||||
requestID = uuid.New().String()
|
||||
ctx = context.WithValue(ctx, soap.RequestIDKey{}, requestID)
|
||||
}
|
||||
|
||||
// Crear request
|
||||
soapRequest := GetDataSoapRequest{
|
||||
Request: request,
|
||||
}
|
||||
|
||||
// Registrar solicitud
|
||||
c.logger.Debug("Enviando solicitud GetData a API1",
|
||||
logger.NewField("request_id", requestID),
|
||||
logger.NewField("parameters", request),
|
||||
)
|
||||
|
||||
// Respuesta
|
||||
response := &GetDataSoapResponse{}
|
||||
|
||||
// Realizar llamada SOAP
|
||||
startTime := time.Now()
|
||||
err := c.soapClient.Call(ctx, "GetData", soapRequest, response)
|
||||
duration := time.Since(startTime)
|
||||
|
||||
// Manejar error
|
||||
if err != nil {
|
||||
c.logger.Error("Error al realizar solicitud GetData a API1",
|
||||
logger.NewField("request_id", requestID),
|
||||
logger.NewField("error", err),
|
||||
logger.NewField("duration_ms", duration.Milliseconds()),
|
||||
)
|
||||
return nil, errors.Wrap(err, "error en solicitud GetData a API1")
|
||||
}
|
||||
|
||||
// Registrar respuesta exitosa
|
||||
c.logger.Debug("Respuesta recibida de API1 para GetData",
|
||||
logger.NewField("request_id", requestID),
|
||||
logger.NewField("duration_ms", duration.Milliseconds()),
|
||||
logger.NewField("status", response.Response.Status),
|
||||
)
|
||||
|
||||
return &response.Response, nil
|
||||
}
|
44
api-soap-facturacion/internal/soap/api/models.go
Normal file
44
api-soap-facturacion/internal/soap/api/models.go
Normal file
@ -0,0 +1,44 @@
|
||||
package api1
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// GetDataRequest es la solicitud para la operación GetData
|
||||
type GetDataRequest struct {
|
||||
XMLName xml.Name `xml:"GetDataRequest"`
|
||||
Namespace string `xml:"xmlns,attr,omitempty"`
|
||||
ID string `xml:"Id,omitempty"`
|
||||
Type string `xml:"Type,omitempty"`
|
||||
Date string `xml:"Date,omitempty"`
|
||||
// Otros campos según sea necesario
|
||||
}
|
||||
|
||||
// GetDataResponse es la respuesta para la operación GetData
|
||||
type GetDataResponse struct {
|
||||
XMLName xml.Name `xml:"GetDataResponse"`
|
||||
Status string `xml:"Status,omitempty"`
|
||||
ResultCode string `xml:"ResultCode,omitempty"`
|
||||
Description string `xml:"Description,omitempty"`
|
||||
Data []Data `xml:"Data>Item,omitempty"`
|
||||
}
|
||||
|
||||
// Data representa un elemento de datos en la respuesta
|
||||
type Data struct {
|
||||
ID string `xml:"Id,omitempty"`
|
||||
Name string `xml:"Name,omitempty"`
|
||||
Value string `xml:"Value,omitempty"`
|
||||
// Otros campos según sea necesario
|
||||
}
|
||||
|
||||
// GetDataSoapRequest envuelve la solicitud para SOAP
|
||||
type GetDataSoapRequest struct {
|
||||
XMLName xml.Name `xml:"api1:GetData"`
|
||||
Request GetDataRequest `xml:",omitempty"`
|
||||
}
|
||||
|
||||
// GetDataSoapResponse envuelve la respuesta para SOAP
|
||||
type GetDataSoapResponse struct {
|
||||
XMLName xml.Name `xml:"GetDataResponse"`
|
||||
Response GetDataResponse `xml:",omitempty"`
|
||||
}
|
96
api-soap-facturacion/internal/soap/api2/client.go
Normal file
96
api-soap-facturacion/internal/soap/api2/client.go
Normal file
@ -0,0 +1,96 @@
|
||||
package api2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"api-soap-facturacion/internal/config"
|
||||
"api-soap-facturacion/internal/logger"
|
||||
"api-soap-facturacion/pkg/errors"
|
||||
"api-soap-facturacion/pkg/soap"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Client es la interfaz para el cliente de API2
|
||||
type Client interface {
|
||||
ProcessTransaction(ctx context.Context, request TransactionRequest) (*TransactionResponse, error)
|
||||
// Agregar más métodos según sea necesario
|
||||
}
|
||||
|
||||
// client implementa la interfaz Client
|
||||
type client struct {
|
||||
soapClient soap.Client
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// crea un nuevo cliente para API2
|
||||
func NewClient(config config.APIConfig, log logger.Logger) Client {
|
||||
// Configurar cliente SOAP
|
||||
options := soap.ClientOptions{
|
||||
Endpoint: config.Endpoint,
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
Timeout: config.Timeout * time.Second,
|
||||
Headers: map[string]string{
|
||||
"User-Agent": "soap-api-client/1.0",
|
||||
},
|
||||
}
|
||||
|
||||
return &client{
|
||||
soapClient: soap.NewClient(options),
|
||||
logger: log,
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessTransaction realiza una solicitud de procesamiento de transacción a API2
|
||||
func (c *client) ProcessTransaction(ctx context.Context, request TransactionRequest) (*TransactionResponse, error) {
|
||||
// Generar ID de solicitud si no existe
|
||||
var requestID string
|
||||
if id, ok := ctx.Value(soap.RequestIDKey{}).(string); ok {
|
||||
requestID = id
|
||||
} else {
|
||||
requestID = uuid.New().String()
|
||||
ctx = context.WithValue(ctx, soap.RequestIDKey{}, requestID)
|
||||
}
|
||||
|
||||
// Crear request
|
||||
soapRequest := TransactionSoapRequest{
|
||||
Request: request,
|
||||
}
|
||||
|
||||
// Registrar solicitud
|
||||
c.logger.Debug("Enviando solicitud de transacción a API2",
|
||||
logger.NewField("request_id", requestID),
|
||||
logger.NewField("transaction_id", request.TransactionID),
|
||||
logger.NewField("amount", request.Amount),
|
||||
)
|
||||
|
||||
// Respuesta
|
||||
response := &TransactionSoapResponse{}
|
||||
|
||||
// Realizar llamada SOAP
|
||||
startTime := time.Now()
|
||||
err := c.soapClient.Call(ctx, "ProcessTransaction", soapRequest, response)
|
||||
duration := time.Since(startTime)
|
||||
|
||||
// Manejar error
|
||||
if err != nil {
|
||||
c.logger.Error("Error al realizar solicitud de transacción a API2",
|
||||
logger.NewField("request_id", requestID),
|
||||
logger.NewField("transaction_id", request.TransactionID),
|
||||
logger.NewField("error", err),
|
||||
logger.NewField("duration_ms", duration.Milliseconds()),
|
||||
)
|
||||
return nil, errors.Wrap(err, "error en solicitud de transacción a API2")
|
||||
}
|
||||
|
||||
// Registrar respuesta exitosa
|
||||
c.logger.Debug("Respuesta recibida de API2 para transacción",
|
||||
logger.NewField("request_id", requestID),
|
||||
logger.NewField("transaction_id", request.TransactionID),
|
||||
logger.NewField("duration_ms", duration.Milliseconds()),
|
||||
logger.NewField("result_code", response.Response.ResultCode),
|
||||
)
|
||||
|
||||
return &response.Response, nil
|
||||
}
|
40
api-soap-facturacion/internal/soap/api2/models.go
Normal file
40
api-soap-facturacion/internal/soap/api2/models.go
Normal file
@ -0,0 +1,40 @@
|
||||
package api2
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// TransactionRequest es la solicitud para procesar una transacción
|
||||
type TransactionRequest struct {
|
||||
XMLName xml.Name `xml:"TransactionRequest"`
|
||||
Namespace string `xml:"xmlns,attr,omitempty"`
|
||||
TransactionID string `xml:"TransactionId,omitempty"`
|
||||
AccountID string `xml:"AccountId,omitempty"`
|
||||
Amount float64 `xml:"Amount,omitempty"`
|
||||
Currency string `xml:"Currency,omitempty"`
|
||||
Description string `xml:"Description,omitempty"`
|
||||
// Otros campos según sea necesario
|
||||
}
|
||||
|
||||
// TransactionResponse es la respuesta para una solicitud de transacción
|
||||
type TransactionResponse struct {
|
||||
XMLName xml.Name `xml:"TransactionResponse"`
|
||||
ResultCode string `xml:"ResultCode,omitempty"`
|
||||
ResultMessage string `xml:"ResultMessage,omitempty"`
|
||||
TransactionID string `xml:"TransactionId,omitempty"`
|
||||
ConfirmationCode string `xml:"ConfirmationCode,omitempty"`
|
||||
Timestamp string `xml:"Timestamp,omitempty"`
|
||||
// Otros campos según sea necesario
|
||||
}
|
||||
|
||||
// TransactionSoapRequest envuelve la solicitud para SOAP
|
||||
type TransactionSoapRequest struct {
|
||||
XMLName xml.Name `xml:"api2:ProcessTransaction"`
|
||||
Request TransactionRequest `xml:",omitempty"`
|
||||
}
|
||||
|
||||
// TransactionSoapResponse envuelve la respuesta para SOAP
|
||||
type TransactionSoapResponse struct {
|
||||
XMLName xml.Name `xml:"ProcessTransactionResponse"`
|
||||
Response TransactionResponse `xml:",omitempty"`
|
||||
}
|
50
api-soap-facturacion/internal/soap/client.go
Normal file
50
api-soap-facturacion/internal/soap/client.go
Normal file
@ -0,0 +1,50 @@
|
||||
package soap
|
||||
|
||||
import (
|
||||
"api-soap-facturacion/internal/config"
|
||||
"api-soap-facturacion/internal/logger"
|
||||
"api-soap-facturacion/internal/soap/api"
|
||||
"api-soap-facturacion/internal/soap/api2"
|
||||
factOp "api-soap-facturacion/internal/soap/facturacionOperaciones"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Clients contiene todos los clientes SOAP disponibles
|
||||
type Clients struct {
|
||||
API1 api1.Client
|
||||
API2 api2.Client
|
||||
API_FACTURACION_OPERACION factOp.Client
|
||||
}
|
||||
|
||||
// crea nuevos clientes SOAP para todas las APIs configuradas
|
||||
func NewSoapClients(cfg *config.Config, log logger.Logger) *Clients {
|
||||
soapLogger := log.With(logger.NewField("component", "soap_client"))
|
||||
|
||||
// Crear cliente para API1
|
||||
api1Client := api1.NewClient(
|
||||
cfg.Soap.APIs["api1"],
|
||||
soapLogger.With(logger.NewField("api", "api1")),
|
||||
)
|
||||
|
||||
// Crear cliente para API2
|
||||
api2Client := api2.NewClient(
|
||||
cfg.Soap.APIs["api2"],
|
||||
soapLogger.With(logger.NewField("api", "api2")),
|
||||
)
|
||||
|
||||
// Configurar opciones del cliente
|
||||
options := &factOp.ClientOptions{
|
||||
Timeout: 45 * time.Second,
|
||||
// También puedes configurar un cliente HTTP personalizado si necesitas
|
||||
// manejar proxies, certificados personalizados, etc.
|
||||
}
|
||||
|
||||
// Registro Codigo Punto de Venta
|
||||
apifactOperacion := factOp.NewClient(options, cfg.Soap.APIs["facturacionOperaciones"], soapLogger.With(logger.NewField("api", "registroPuntoVenta")))
|
||||
|
||||
return &Clients{
|
||||
API1: api1Client,
|
||||
API2: api2Client,
|
||||
API_FACTURACION_OPERACION: *apifactOperacion,
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package facturacionOperaciones
|
||||
|
||||
import (
|
||||
"api-soap-facturacion/internal/logger"
|
||||
"api-soap-facturacion/pkg/soap"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClientOptions contiene opciones para personalizar el cliente SOAP
|
||||
type ClientOptions struct {
|
||||
Timeout time.Duration
|
||||
HTTPClient *http.Client
|
||||
EndpointURL string
|
||||
}
|
||||
|
||||
// Client es el cliente SOAP para comunicarse con SIAT
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
endpointURL string
|
||||
soapClient soap.Client
|
||||
logger logger.Logger
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package facturacionOperaciones
|
||||
|
||||
import (
|
||||
"api-soap-facturacion/internal/config"
|
||||
"api-soap-facturacion/internal/logger"
|
||||
mfo "api-soap-facturacion/internal/models/facturacionOperaciones"
|
||||
"api-soap-facturacion/pkg/soap"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Constantes de configuración
|
||||
const (
|
||||
DefaultTimeout = 30 * time.Second
|
||||
ContentType = "text/xml;charset=UTF-8"
|
||||
SoapAction = "https://siat.impuestos.gob.bo/registroPuntoVenta"
|
||||
SiatEndpoint = "https://pilotosiatservicios.impuestos.gob.bo/v2/FacturacionOperaciones"
|
||||
)
|
||||
|
||||
// NewClient crea una nueva instancia del cliente SOAP
|
||||
func NewClient(options *ClientOptions, config config.APIConfig, log logger.Logger) *Client {
|
||||
|
||||
// Configurar cliente SOAP
|
||||
factOperaciones := soap.ClientOptions{
|
||||
Endpoint: config.Endpoint,
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
Timeout: config.Timeout * time.Second,
|
||||
Headers: map[string]string{
|
||||
"User-Agent": "soap-api-client/1.0",
|
||||
},
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
endpointURL: SiatEndpoint,
|
||||
soapClient: soap.NewClient(factOperaciones),
|
||||
logger: log,
|
||||
}
|
||||
|
||||
if options != nil {
|
||||
if options.EndpointURL != "" {
|
||||
client.endpointURL = options.EndpointURL
|
||||
}
|
||||
|
||||
if options.HTTPClient != nil {
|
||||
client.httpClient = options.HTTPClient
|
||||
}
|
||||
}
|
||||
|
||||
if client.httpClient == nil {
|
||||
timeout := DefaultTimeout
|
||||
if options != nil && options.Timeout > 0 {
|
||||
timeout = options.Timeout
|
||||
}
|
||||
|
||||
client.httpClient = &http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// envía una solicitud para registrar un punto de venta en SIAT
|
||||
func (c *Client) RegistrarPuntoVenta(ctx context.Context, solicitud mfo.SolicitudRegistroPuntoVenta) (*mfo.RespuestaRegistroPuntoVenta, error) {
|
||||
// Preparar la petición SOAP
|
||||
request := mfo.RegistroPuntoVentaRequest{
|
||||
SoapEnv: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
Siat: "https://siat.impuestos.gob.bo/",
|
||||
}
|
||||
request.Body.RegistroPuntoVenta.Solicitud = solicitud
|
||||
|
||||
// Convertir la petición a XML
|
||||
requestBody, err := xml.MarshalIndent(request, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error al convertir la solicitud a XML: %w", err)
|
||||
}
|
||||
|
||||
// Crear la petición HTTP con el contexto
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpointURL, bytes.NewReader(requestBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error al crear la petición HTTP: %w", err)
|
||||
}
|
||||
|
||||
// Configurar encabezados
|
||||
req.Header.Set("Content-Type", ContentType)
|
||||
req.Header.Set("SOAPAction", SoapAction)
|
||||
|
||||
// Realizar la petición HTTP
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error al realizar la petición HTTP: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Leer el cuerpo de la respuesta
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error al leer la respuesta: %w", err)
|
||||
}
|
||||
|
||||
// Verificar el código de estado HTTP
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("error en la respuesta HTTP: %s, cuerpo: %s", resp.Status, respBody)
|
||||
}
|
||||
|
||||
// Parsear la respuesta
|
||||
var soapResponse mfo.RegistroPuntoVentaResponse
|
||||
if err := xml.Unmarshal(respBody, &soapResponse); err != nil {
|
||||
return nil, fmt.Errorf("error al parsear la respuesta XML: %w, cuerpo: %s", err, respBody)
|
||||
}
|
||||
|
||||
return &soapResponse.Body.RegistroPuntoVentaResponse.Respuesta, nil
|
||||
}
|
59
api-soap-facturacion/logs/api_facturacion.log
Normal file
59
api-soap-facturacion/logs/api_facturacion.log
Normal file
@ -0,0 +1,59 @@
|
||||
INFO: 2025/05/15 15:07:35 cmd/api/main.go:29: [api-facturacion-myapps] Iniciando aplicación {"app":"api-facturacion-myapps"}
|
||||
INFO: 2025/05/15 15:07:35 cmd/api/main.go:43: [api-facturacion-myapps] Conexión a la base de datos establecida
|
||||
INFO: 2025/05/15 15:07:35 cmd/api/main.go:50: [api-facturacion-myapps] Parámetros cargados desde la base de datos {"count":2}
|
||||
INFO: 2025/05/15 15:07:35 cmd/api/main.go:72: [api-facturacion-myapps] Servidor iniciado {"port":9999}
|
||||
INFO: 2025/05/15 15:12:58 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
DEBUG: 2025/05/15 15:12:58 internal/soap/api/client.go:62: [api-facturacion-myapps] Enviando solicitud GetData a API1 {"parameters":{"XMLName":{"Space":"","Local":""},"Namespace":"http://api1.example.com/soap","ID":"12345","Type":"CUSTOMER","Date":"2023-12-01"},"request_id":"req-18515d73-aa22-4f2e-a71e-fe5e4233b340"}
|
||||
ERROR: 2025/05/15 15:12:58 internal/soap/api/client.go:77: [api-facturacion-myapps] Error al realizar solicitud GetData a API1 {"duration_ms":9,"error":"error sending HTTP request: Post \"https://api1.example.com/soap\": proxyconnect tcp: dial tcp [::1]:80: connectex: No se puede establecer una conexión ya que el equipo de destino denegó expresamente dicha conexión.","errorVerbose":"Post \"https://api1.example.com/soap\": proxyconnect tcp: dial tcp [::1]:80: connectex: No se puede establecer una conexión ya que el equipo de destino denegó expresamente dicha conexión.\nerror sending HTTP request\napi-soap-facturacion/pkg/soap.(*client).Call\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/pkg/soap/soap.go:146\napi-soap-facturacion/internal/soap/api.(*client).GetData\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/soap/api/client.go:72\napi-soap-facturacion/internal/api.(*Handler).API1GetData\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/handler.go:97\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\napi-soap-facturacion/internal/api.NewRouter.CORSMiddleware.func3\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/router.go:122\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\napi-soap-facturacion/internal/api.NewRouter.RequestIDMiddleware.func2\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/router.go:106\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\napi-soap-facturacion/internal/api.NewRouter.LoggerMiddleware.func1\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/router.go:84\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\ngithub.com/gin-gonic/gin.CustomRecoveryWithWriter.func1\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/recovery.go:102\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\ngithub.com/gin-gonic/gin.(*Engine).handleHTTPRequest\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:633\ngithub.com/gin-gonic/gin.(*Engine).ServeHTTP\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:589\nnet/http.serverHandler.ServeHTTP\n\tC:/Users/USUARIO/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.9.windows-amd64/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\tC:/Users/USUARIO/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.9.windows-amd64/src/net/http/server.go:2092\nruntime.goexit\n\tC:/Users/USUARIO/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.9.windows-amd64/src/runtime/asm_amd64.s:1700","request_id":"req-18515d73-aa22-4f2e-a71e-fe5e4233b340"}
|
||||
ERROR: 2025/05/15 15:12:58 internal/api/handler.go:99: [api-facturacion-myapps] Error al llamar a API1 {"error":"error en solicitud GetData a API1: error sending HTTP request: Post \"https://api1.example.com/soap\": proxyconnect tcp: dial tcp [::1]:80: connectex: No se puede establecer una conexión ya que el equipo de destino denegó expresamente dicha conexión."}
|
||||
INFO: 2025/05/15 15:12:58 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":500}
|
||||
INFO: 2025/05/15 15:28:45 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
INFO: 2025/05/15 15:28:45 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":200}
|
||||
INFO: 2025/05/15 15:33:43 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
INFO: 2025/05/15 15:33:43 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":200}
|
||||
INFO: 2025/05/15 15:33:49 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
INFO: 2025/05/15 15:33:49 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":200}
|
||||
INFO: 2025/05/15 15:55:19 cmd/api/main.go:82: [api-facturacion-myapps] Apagando servidor...
|
||||
INFO: 2025/05/15 15:55:19 cmd/api/main.go:90: [api-facturacion-myapps] Servidor detenido correctamente
|
||||
INFO: 2025/05/15 16:06:07 cmd/api/main.go:28: [api-facturacion-myapps] Iniciando aplicación {"app":"api-facturacion-myapps"}
|
||||
INFO: 2025/05/15 16:06:07 cmd/api/main.go:42: [api-facturacion-myapps] Conexión a la base de datos establecida
|
||||
INFO: 2025/05/15 16:06:07 cmd/api/main.go:49: [api-facturacion-myapps] Parámetros cargados desde la base de datos {"count":2}
|
||||
INFO: 2025/05/15 16:06:07 cmd/api/main.go:71: [api-facturacion-myapps] Servidor iniciado {"port":9999}
|
||||
INFO: 2025/05/15 16:09:17 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
INFO: 2025/05/15 16:09:17 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":200}
|
||||
INFO: 2025/05/15 16:09:21 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
INFO: 2025/05/15 16:09:21 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":200}
|
||||
INFO: 2025/05/15 16:09:24 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
INFO: 2025/05/15 16:09:24 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":200}
|
||||
INFO: 2025/05/15 16:09:28 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
INFO: 2025/05/15 16:09:28 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":200}
|
||||
INFO: 2025/05/15 16:09:33 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
DEBUG: 2025/05/15 16:09:33 internal/soap/api/client.go:62: [api-facturacion-myapps] Enviando solicitud GetData a API1 {"parameters":{"XMLName":{"Space":"","Local":""},"Namespace":"http://api1.example.com/soap","ID":"12345","Type":"CUSTOMER","Date":"2023-12-01"},"request_id":"req-494f9fbb-01d5-4e9a-b792-79325d7a84c2"}
|
||||
ERROR: 2025/05/15 16:09:33 internal/soap/api/client.go:77: [api-facturacion-myapps] Error al realizar solicitud GetData a API1 {"duration_ms":10,"error":"error sending HTTP request: Post \"https://new-api1.example.com/soap\": proxyconnect tcp: dial tcp [::1]:80: connectex: No se puede establecer una conexión ya que el equipo de destino denegó expresamente dicha conexión.","errorVerbose":"Post \"https://new-api1.example.com/soap\": proxyconnect tcp: dial tcp [::1]:80: connectex: No se puede establecer una conexión ya que el equipo de destino denegó expresamente dicha conexión.\nerror sending HTTP request\napi-soap-facturacion/pkg/soap.(*client).Call\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/pkg/soap/soap.go:160\napi-soap-facturacion/internal/soap/api.(*client).GetData\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/soap/api/client.go:72\napi-soap-facturacion/internal/api.(*Handler).API1GetData\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/handler.go:97\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\napi-soap-facturacion/internal/api.CORSMiddleware.func1\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/router.go:122\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\napi-soap-facturacion/internal/api.RequestIDMiddleware.func1\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/router.go:106\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\napi-soap-facturacion/internal/api.LoggerMiddleware.func1\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/router.go:84\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\ngithub.com/gin-gonic/gin.CustomRecoveryWithWriter.func1\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/recovery.go:102\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\ngithub.com/gin-gonic/gin.(*Engine).handleHTTPRequest\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:633\ngithub.com/gin-gonic/gin.(*Engine).ServeHTTP\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:589\nnet/http.serverHandler.ServeHTTP\n\tC:/Users/USUARIO/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.9.windows-amd64/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\tC:/Users/USUARIO/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.9.windows-amd64/src/net/http/server.go:2092\nruntime.goexit\n\tC:/Users/USUARIO/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.9.windows-amd64/src/runtime/asm_amd64.s:1700","request_id":"req-494f9fbb-01d5-4e9a-b792-79325d7a84c2"}
|
||||
ERROR: 2025/05/15 16:09:33 internal/api/handler.go:99: [api-facturacion-myapps] Error al llamar a API1 {"error":"error en solicitud GetData a API1: error sending HTTP request: Post \"https://new-api1.example.com/soap\": proxyconnect tcp: dial tcp [::1]:80: connectex: No se puede establecer una conexión ya que el equipo de destino denegó expresamente dicha conexión."}
|
||||
INFO: 2025/05/15 16:09:33 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":500}
|
||||
INFO: 2025/05/15 16:14:58 cmd/api/main.go:81: [api-facturacion-myapps] Apagando servidor...
|
||||
INFO: 2025/05/15 16:14:58 cmd/api/main.go:89: [api-facturacion-myapps] Servidor detenido correctamente
|
||||
INFO: 2025/05/15 16:15:50 cmd/api/main.go:28: [api-facturacion-myapps] Iniciando aplicación {"app":"api-facturacion-myapps"}
|
||||
INFO: 2025/05/15 16:15:50 cmd/api/main.go:42: [api-facturacion-myapps] Conexión a la base de datos establecida
|
||||
INFO: 2025/05/15 16:15:50 cmd/api/main.go:49: [api-facturacion-myapps] Parámetros cargados desde la base de datos {"count":2}
|
||||
INFO: 2025/05/15 16:15:50 cmd/api/main.go:71: [api-facturacion-myapps] Servidor iniciado {"port":9999}
|
||||
INFO: 2025/05/15 16:16:00 internal/api/router.go:81: [api-facturacion-myapps] Solicitud iniciada
|
||||
DEBUG: 2025/05/15 16:16:10 internal/soap/api/client.go:62: [api-facturacion-myapps] Enviando solicitud GetData a API1 {"parameters":{"XMLName":{"Space":"","Local":""},"Namespace":"http://api1.example.com/soap","ID":"12345","Type":"CUSTOMER","Date":"2023-12-01"},"request_id":"req-88f62aab-f0eb-4e7b-a82c-d1b7aed9c279"}
|
||||
ERROR: 2025/05/15 16:16:10 internal/soap/api/client.go:77: [api-facturacion-myapps] Error al realizar solicitud GetData a API1 {"duration_ms":16,"error":"error sending HTTP request: Post \"https://new-api1.example.com/soap\": proxyconnect tcp: dial tcp [::1]:80: connectex: No se puede establecer una conexión ya que el equipo de destino denegó expresamente dicha conexión.","errorVerbose":"Post \"https://new-api1.example.com/soap\": proxyconnect tcp: dial tcp [::1]:80: connectex: No se puede establecer una conexión ya que el equipo de destino denegó expresamente dicha conexión.\nerror sending HTTP request\napi-soap-facturacion/pkg/soap.(*client).Call\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/pkg/soap/soap.go:160\napi-soap-facturacion/internal/soap/api.(*client).GetData\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/soap/api/client.go:72\napi-soap-facturacion/internal/api.(*Handler).API1GetData\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/getDataHandler.go:50\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\napi-soap-facturacion/internal/api.CORSMiddleware.func1\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/router.go:122\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\napi-soap-facturacion/internal/api.RequestIDMiddleware.func1\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/router.go:106\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\napi-soap-facturacion/internal/api.LoggerMiddleware.func1\n\tC:/Users/USUARIO/Documents/00000-MYAPPS/00-SISTEMA_FACTURACION/000-FACTURACION/api-soap-facturacion/internal/api/router.go:84\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\ngithub.com/gin-gonic/gin.CustomRecoveryWithWriter.func1\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/recovery.go:102\ngithub.com/gin-gonic/gin.(*Context).Next\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\ngithub.com/gin-gonic/gin.(*Engine).handleHTTPRequest\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:633\ngithub.com/gin-gonic/gin.(*Engine).ServeHTTP\n\tC:/Users/USUARIO/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:589\nnet/http.serverHandler.ServeHTTP\n\tC:/Users/USUARIO/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.9.windows-amd64/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\tC:/Users/USUARIO/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.9.windows-amd64/src/net/http/server.go:2092\nruntime.goexit\n\tC:/Users/USUARIO/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.9.windows-amd64/src/runtime/asm_amd64.s:1700","request_id":"req-88f62aab-f0eb-4e7b-a82c-d1b7aed9c279"}
|
||||
ERROR: 2025/05/15 16:16:10 internal/api/getDataHandler.go:52: [api-facturacion-myapps] Error al llamar a API1 {"error":"error en solicitud GetData a API1: error sending HTTP request: Post \"https://new-api1.example.com/soap\": proxyconnect tcp: dial tcp [::1]:80: connectex: No se puede establecer una conexión ya que el equipo de destino denegó expresamente dicha conexión."}
|
||||
INFO: 2025/05/15 16:16:14 internal/api/router.go:88: [api-facturacion-myapps] Solicitud completada {"status":500}
|
||||
INFO: 2025/05/15 16:28:50 cmd/api/main.go:81: [api-facturacion-myapps] Apagando servidor...
|
||||
INFO: 2025/05/15 16:28:50 cmd/api/main.go:89: [api-facturacion-myapps] Servidor detenido correctamente
|
||||
INFO: 2025/05/15 16:28:54 cmd/api/main.go:28: [api-facturacion-myapps] Iniciando aplicación {"app":"api-facturacion-myapps"}
|
||||
INFO: 2025/05/15 16:28:54 cmd/api/main.go:42: [api-facturacion-myapps] Conexión a la base de datos establecida
|
||||
INFO: 2025/05/15 16:28:54 cmd/api/main.go:49: [api-facturacion-myapps] Parámetros cargados desde la base de datos {"count":2}
|
||||
INFO: 2025/05/15 16:28:54 cmd/api/main.go:71: [api-facturacion-myapps] Servidor iniciado {"port":9999}
|
||||
INFO: 2025/05/15 16:28:57 cmd/api/main.go:81: [api-facturacion-myapps] Apagando servidor...
|
||||
INFO: 2025/05/15 16:28:57 cmd/api/main.go:89: [api-facturacion-myapps] Servidor detenido correctamente
|
||||
INFO: 2025/05/15 16:31:23 cmd/api/main.go:28: [api-facturacion-myapps] Iniciando aplicación {"app":"api-facturacion-myapps"}
|
||||
INFO: 2025/05/15 16:31:23 cmd/api/main.go:42: [api-facturacion-myapps] Conexión a la base de datos establecida
|
||||
INFO: 2025/05/15 16:31:23 cmd/api/main.go:49: [api-facturacion-myapps] Parámetros cargados desde la base de datos {"count":2}
|
||||
INFO: 2025/05/15 16:31:23 cmd/api/main.go:71: [api-facturacion-myapps] Servidor iniciado {"port":9999}
|
||||
INFO: 2025/05/15 17:06:47 cmd/api/main.go:81: [api-facturacion-myapps] Apagando servidor...
|
||||
INFO: 2025/05/15 17:06:47 cmd/api/main.go:89: [api-facturacion-myapps] Servidor detenido correctamente
|
129
api-soap-facturacion/pkg/errors.go
Normal file
129
api-soap-facturacion/pkg/errors.go
Normal file
@ -0,0 +1,129 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Error es una interfaz extendida para errores
|
||||
type Error interface {
|
||||
error
|
||||
Code() string
|
||||
Unwrap() error
|
||||
}
|
||||
|
||||
// CustomError implementa la interfaz Error
|
||||
type CustomError struct {
|
||||
code string
|
||||
message string
|
||||
cause error
|
||||
stack []uintptr
|
||||
}
|
||||
|
||||
// New crea un nuevo error personalizado
|
||||
func New(code, message string) Error {
|
||||
return &CustomError{
|
||||
code: code,
|
||||
message: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap envuelve un error con información adicional
|
||||
func Wrap(err error, message string) Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &CustomError{
|
||||
code: extractCode(err),
|
||||
message: message,
|
||||
cause: err,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf envuelve un error con un mensaje formateado
|
||||
func Wrapf(err error, format string, args ...interface{}) Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &CustomError{
|
||||
code: extractCode(err),
|
||||
message: fmt.Sprintf(format, args...),
|
||||
cause: err,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewWithCode crea un nuevo error con un código específico
|
||||
func NewWithCode(code, message string) Error {
|
||||
return &CustomError{
|
||||
code: code,
|
||||
message: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Error implementa la interfaz error
|
||||
func (e *CustomError) Error() string {
|
||||
if e.cause != nil {
|
||||
return fmt.Sprintf("%s: %v", e.message, e.cause)
|
||||
}
|
||||
return e.message
|
||||
}
|
||||
|
||||
// Code retorna el código de error
|
||||
func (e *CustomError) Code() string {
|
||||
return e.code
|
||||
}
|
||||
|
||||
// Unwrap implementa la interfaz de unwrapping
|
||||
func (e *CustomError) Unwrap() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
// Stack retorna la pila de llamadas
|
||||
func (e *CustomError) Stack() string {
|
||||
var sb strings.Builder
|
||||
frames := runtime.CallersFrames(e.stack)
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
sb.WriteString(fmt.Sprintf("%s:%d %s\n", frame.File, frame.Line, frame.Function))
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// extractCode extrae el código de un error
|
||||
func extractCode(err error) string {
|
||||
if customErr, ok := err.(Error); ok {
|
||||
return customErr.Code()
|
||||
}
|
||||
return "UNKNOWN"
|
||||
}
|
||||
|
||||
// callers obtiene la pila de llamadas
|
||||
func callers() []uintptr {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
return pcs[0:n]
|
||||
}
|
||||
|
||||
// Errores comunes
|
||||
var (
|
||||
ErrNotFound = NewWithCode("NOT_FOUND", "recurso no encontrado")
|
||||
ErrUnauthorized = NewWithCode("UNAUTHORIZED", "no autorizado")
|
||||
ErrForbidden = NewWithCode("FORBIDDEN", "acceso prohibido")
|
||||
ErrBadRequest = NewWithCode("BAD_REQUEST", "solicitud incorrecta")
|
||||
ErrInternal = NewWithCode("INTERNAL", "error interno del servidor")
|
||||
ErrTimeout = NewWithCode("TIMEOUT", "tiempo de espera agotado")
|
||||
ErrNotImplemented = NewWithCode("NOT_IMPLEMENTED", "no implementado")
|
||||
)
|
127
api-soap-facturacion/pkg/errors/errors.go
Normal file
127
api-soap-facturacion/pkg/errors/errors.go
Normal file
@ -0,0 +1,127 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Error es una interfaz extendida para errores
|
||||
type Error interface {
|
||||
error
|
||||
Code() string
|
||||
Unwrap() error
|
||||
}
|
||||
|
||||
// CustomError implementa la interfaz Error
|
||||
type CustomError struct {
|
||||
code string
|
||||
message string
|
||||
cause error
|
||||
stack []uintptr
|
||||
}
|
||||
|
||||
// New crea un nuevo error personalizado
|
||||
func New(code, message string) Error {
|
||||
return &CustomError{
|
||||
code: code,
|
||||
message: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap envuelve un error con información adicional
|
||||
func Wrap(err error, message string) Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &CustomError{
|
||||
code: extractCode(err),
|
||||
message: message,
|
||||
cause: err,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf envuelve un error con un mensaje formateado
|
||||
func Wrapf(err error, format string, args ...interface{}) Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &CustomError{
|
||||
code: extractCode(err),
|
||||
message: fmt.Sprintf(format, args...),
|
||||
cause: err,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewWithCode crea un nuevo error con un código específico
|
||||
func NewWithCode(code, message string) Error {
|
||||
return &CustomError{
|
||||
code: code,
|
||||
message: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Error implementa la interfaz error
|
||||
func (e *CustomError) Error() string {
|
||||
if e.cause != nil {
|
||||
return fmt.Sprintf("%s: %v", e.message, e.cause)
|
||||
}
|
||||
return e.message
|
||||
}
|
||||
|
||||
// Code retorna el código de error
|
||||
func (e *CustomError) Code() string {
|
||||
return e.code
|
||||
}
|
||||
|
||||
// Unwrap implementa la interfaz de unwrapping
|
||||
func (e *CustomError) Unwrap() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
// Stack retorna la pila de llamadas
|
||||
func (e *CustomError) Stack() string {
|
||||
var sb strings.Builder
|
||||
frames := runtime.CallersFrames(e.stack)
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
sb.WriteString(fmt.Sprintf("%s:%d %s\n", frame.File, frame.Line, frame.Function))
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// extractCode extrae el código de un error
|
||||
func extractCode(err error) string {
|
||||
if customErr, ok := err.(Error); ok {
|
||||
return customErr.Code()
|
||||
}
|
||||
return "UNKNOWN"
|
||||
}
|
||||
|
||||
// callers obtiene la pila de llamadas
|
||||
func callers() []uintptr {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
return pcs[0:n]
|
||||
}
|
||||
|
||||
// Errores comunes
|
||||
var (
|
||||
ErrNotFound = NewWithCode("NOT_FOUND", "recurso no encontrado")
|
||||
ErrUnauthorized = NewWithCode("UNAUTHORIZED", "no autorizado")
|
||||
ErrForbidden = NewWithCode("FORBIDDEN", "acceso prohibido")
|
||||
ErrBadRequest = NewWithCode("BAD_REQUEST", "solicitud incorrecta")
|
||||
ErrInternal = NewWithCode("INTERNAL", "error interno del servidor")
|
||||
ErrTimeout = NewWithCode("TIMEOUT", "tiempo de espera agotado")
|
||||
ErrNotImplemented = NewWithCode("NOT_IMPLEMENTED", "no implementado")
|
||||
)
|
200
api-soap-facturacion/pkg/soap/soap.go
Normal file
200
api-soap-facturacion/pkg/soap/soap.go
Normal file
@ -0,0 +1,200 @@
|
||||
package soap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Client es la interfaz para un cliente SOAP
|
||||
type Client interface {
|
||||
Call(ctx context.Context, operation string, request interface{}, response interface{}) error
|
||||
}
|
||||
|
||||
// ClientOptions contiene opciones para el cliente SOAP
|
||||
type ClientOptions struct {
|
||||
Endpoint string
|
||||
Username string
|
||||
Password string
|
||||
Timeout time.Duration
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
// RequestIDKey es la clave para el ID de solicitud en el contexto
|
||||
type RequestIDKey struct{}
|
||||
|
||||
// client implementa la interfaz Client
|
||||
//type client struct {
|
||||
// endpoint string
|
||||
// httpClient *http.Client
|
||||
// credentials *credentials
|
||||
// headers map[string]string
|
||||
//}
|
||||
|
||||
// credentials contiene las credenciales para autenticación
|
||||
type credentials struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
// Añadir este método al final del archivo pkg/soap/soap.go
|
||||
// En la definición del cliente:
|
||||
type client struct {
|
||||
endpoint string
|
||||
httpClient *http.Client
|
||||
credentials *credentials
|
||||
headers map[string]string
|
||||
}
|
||||
|
||||
// Endpoint retorna el endpoint actual del cliente
|
||||
func (c *client) Endpoint() string {
|
||||
return c.endpoint
|
||||
}
|
||||
|
||||
// NewClient crea un nuevo cliente SOAP
|
||||
func NewClient(options ClientOptions) Client {
|
||||
httpClient := &http.Client{
|
||||
Timeout: options.Timeout,
|
||||
}
|
||||
|
||||
var creds *credentials
|
||||
if options.Username != "" && options.Password != "" {
|
||||
creds = &credentials{
|
||||
username: options.Username,
|
||||
password: options.Password,
|
||||
}
|
||||
}
|
||||
|
||||
return &client{
|
||||
endpoint: options.Endpoint,
|
||||
httpClient: httpClient,
|
||||
credentials: creds,
|
||||
headers: options.Headers,
|
||||
}
|
||||
}
|
||||
|
||||
// Envelope es la estructura para el envelope SOAP
|
||||
type Envelope struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
XMLNS string `xml:"xmlns,attr"`
|
||||
Header interface{} `xml:"Header,omitempty"`
|
||||
Body Body `xml:"Body"`
|
||||
}
|
||||
|
||||
// Body es la estructura para el cuerpo SOAP
|
||||
type Body struct {
|
||||
XMLName xml.Name `xml:"Body"`
|
||||
Content interface{} `xml:",omitempty"`
|
||||
Fault *Fault `xml:"Fault,omitempty"`
|
||||
}
|
||||
|
||||
// Fault es la estructura para errores SOAP
|
||||
type Fault struct {
|
||||
XMLName xml.Name `xml:"Fault"`
|
||||
FaultCode string `xml:"faultcode,omitempty"`
|
||||
FaultString string `xml:"faultstring,omitempty"`
|
||||
Detail string `xml:"detail,omitempty"`
|
||||
}
|
||||
|
||||
// Call realiza una llamada SOAP
|
||||
func (c *client) Call(ctx context.Context, operation string, request interface{}, response interface{}) error {
|
||||
// Obtener ID de solicitud del contexto o generar uno nuevo
|
||||
var requestID string
|
||||
if id, ok := ctx.Value(RequestIDKey{}).(string); ok {
|
||||
requestID = id
|
||||
} else {
|
||||
requestID = uuid.New().String()
|
||||
}
|
||||
|
||||
// Crear envelope SOAP
|
||||
envelope := Envelope{
|
||||
XMLNS: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
Body: Body{
|
||||
Content: request,
|
||||
},
|
||||
}
|
||||
|
||||
// Serializar envelope a XML
|
||||
requestBuffer := &bytes.Buffer{}
|
||||
enc := xml.NewEncoder(requestBuffer)
|
||||
enc.Indent("", " ")
|
||||
if err := enc.Encode(envelope); err != nil {
|
||||
return errors.Wrap(err, "error encoding SOAP request")
|
||||
}
|
||||
|
||||
// Crear solicitud HTTP
|
||||
httpReq, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
c.endpoint,
|
||||
requestBuffer,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating HTTP request")
|
||||
}
|
||||
|
||||
// Establecer cabeceras HTTP
|
||||
httpReq.Header.Set("Content-Type", "text/xml; charset=utf-8")
|
||||
httpReq.Header.Set("SOAPAction", operation)
|
||||
httpReq.Header.Set("X-Request-ID", requestID)
|
||||
|
||||
// Agregar cabeceras adicionales
|
||||
for key, value := range c.headers {
|
||||
httpReq.Header.Set(key, value)
|
||||
}
|
||||
|
||||
// Agregar autenticación si es necesario
|
||||
if c.credentials != nil {
|
||||
httpReq.SetBasicAuth(c.credentials.username, c.credentials.password)
|
||||
}
|
||||
|
||||
// Realizar solicitud HTTP
|
||||
httpResp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error sending HTTP request")
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
// Leer respuesta
|
||||
respBody, err := io.ReadAll(httpResp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error reading HTTP response")
|
||||
}
|
||||
|
||||
// Verificar código de estado HTTP
|
||||
if httpResp.StatusCode != http.StatusOK {
|
||||
return errors.Errorf("HTTP error: %d - %s, body: %s",
|
||||
httpResp.StatusCode,
|
||||
httpResp.Status,
|
||||
string(respBody),
|
||||
)
|
||||
}
|
||||
|
||||
// Deserializar respuesta
|
||||
respEnvelope := &Envelope{
|
||||
Body: Body{
|
||||
Content: response,
|
||||
},
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(respBody, respEnvelope); err != nil {
|
||||
return errors.Wrap(err, "error unmarshalling SOAP response")
|
||||
}
|
||||
|
||||
// Verificar si hay un SOAP Fault
|
||||
if respEnvelope.Body.Fault != nil {
|
||||
return errors.Errorf("SOAP Fault: %s - %s, detail: %s",
|
||||
respEnvelope.Body.Fault.FaultCode,
|
||||
respEnvelope.Body.Fault.FaultString,
|
||||
respEnvelope.Body.Fault.Detail,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
37
api-soap-facturacion/scripts/bd.sql
Normal file
37
api-soap-facturacion/scripts/bd.sql
Normal file
@ -0,0 +1,37 @@
|
||||
CREATE TABLE IF NOT EXISTS parameters (
|
||||
id SERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla para registro de solicitudes SOAP
|
||||
CREATE TABLE IF NOT EXISTS soap_requests (
|
||||
id SERIAL PRIMARY KEY,
|
||||
request_id VARCHAR(36) NOT NULL,
|
||||
api_id VARCHAR(50) NOT NULL,
|
||||
operation_name VARCHAR(100) NOT NULL,
|
||||
request_body TEXT NOT NULL,
|
||||
response_body TEXT,
|
||||
status_code INT,
|
||||
error TEXT,
|
||||
request_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
response_time TIMESTAMP WITH TIME ZONE,
|
||||
processing_time BIGINT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Índices
|
||||
CREATE INDEX IF NOT EXISTS idx_soap_requests_request_id ON soap_requests(request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_soap_requests_api_id ON soap_requests(api_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_soap_requests_operation_name ON soap_requests(operation_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_soap_requests_request_time ON soap_requests(request_time);
|
||||
|
||||
-- Insertar parámetros iniciales
|
||||
INSERT INTO parameters (key, value, description)
|
||||
VALUES('soap.api1.endpoint', 'https://api1.example.com/soap', 'Endpoint for API 1'),
|
||||
('soap.api2.endpoint', 'https://api2.example.com/soap', 'Endpoint for API 2')
|
||||
ON CONFLICT (key) DO NOTHING;
|
8
daemonService/.idea/.gitignore
generated
vendored
Normal file
8
daemonService/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
9
daemonService/.idea/daemonService.iml
generated
Normal file
9
daemonService/.idea/daemonService.iml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
daemonService/.idea/modules.xml
generated
Normal file
8
daemonService/.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/daemonService.iml" filepath="$PROJECT_DIR$/.idea/daemonService.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
401
daemonService/cmd/daemon/main.go
Normal file
401
daemonService/cmd/daemon/main.go
Normal file
@ -0,0 +1,401 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/handlers"
|
||||
"daemonService/internal/models"
|
||||
svc "daemonService/internal/models"
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
"daemonService/internal/repositories"
|
||||
"daemonService/internal/services/obtencionCodigo"
|
||||
"daemonService/internal/services/procesar"
|
||||
"daemonService/internal/services/sincronizacionDatos"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/robfig/cron/v3"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"daemonService/internal/api"
|
||||
"daemonService/internal/config"
|
||||
"daemonService/internal/database"
|
||||
"daemonService/internal/notifications"
|
||||
"daemonService/internal/services"
|
||||
)
|
||||
|
||||
func main() {
|
||||
configFile := flag.String("config", "configs/config.yaml", "Ruta al archivo de configuración")
|
||||
runOnce := flag.Bool("run-once", false, "Ejecutar servicios una vez y salir")
|
||||
serviceName := flag.String("service", "", "Nombre del servicio específico a ejecutar")
|
||||
flag.Parse()
|
||||
|
||||
// Cargar configuración
|
||||
cfg, err := config.LoadConfig(*configFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Error al cargar configuración: %v", err)
|
||||
}
|
||||
|
||||
// Inicializar la base de datos
|
||||
db, err := database.InitDB(*cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("Error al inicializar la base de datos: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Crear sistema de notificaciones
|
||||
notifier := notifications.CreateNotificationSender(cfg.Notifications.Enabled, cfg.Notifications.Method, cfg.Notifications.Config)
|
||||
if notifier != nil {
|
||||
log.Printf("Sistema de notificaciones iniciado: %s", cfg.Notifications.Method)
|
||||
}
|
||||
// Crear servicios a partir de la configuración
|
||||
var servicesList []models.SoapService
|
||||
var servicesList2 []obtencionCodigos.CuisService
|
||||
for _, svcCfg := range cfg.Services {
|
||||
if !svcCfg.Enabled {
|
||||
continue
|
||||
}
|
||||
// Crear el logger para el servicio
|
||||
serviceLogger := log.New(log.Writer(), fmt.Sprintf("[%s] ", svcCfg.Name), log.LstdFlags)
|
||||
baseSvc := svc.ServiceModel{
|
||||
Name: svcCfg.Name,
|
||||
Enabled: svcCfg.Enabled,
|
||||
Concurrency: svcCfg.Concurrency,
|
||||
DB: db,
|
||||
SOAPEndpoint: cfg.SOAP.FACTURA_SINCRONIZACION.Endpoint,
|
||||
SOAPTimeout: time.Duration(cfg.SOAP.FACTURA_SINCRONIZACION.Timeout) * time.Second,
|
||||
SOAPRetries: cfg.SOAP.FACTURA_SINCRONIZACION.Retries,
|
||||
NotificationSender: notifier,
|
||||
KeyToken: "",
|
||||
ValueToken: "",
|
||||
TagNames: "",
|
||||
QueryInsert: "",
|
||||
MsgCustomResponse: "",
|
||||
}
|
||||
|
||||
cuisServiceModel := obtencionCodigos.CuisServiceModel{
|
||||
Name: svcCfg.Name,
|
||||
Enabled: svcCfg.Enabled,
|
||||
Concurrency: svcCfg.Concurrency,
|
||||
DB: db,
|
||||
SOAPEndpoint: cfg.SOAP.FACTURA_CODIGO.Endpoint,
|
||||
SOAPTimeout: time.Duration(cfg.SOAP.FACTURA_CODIGO.Timeout) * time.Second,
|
||||
SOAPRetries: cfg.SOAP.FACTURA_CODIGO.Retries,
|
||||
NotificationSender: notifier,
|
||||
TokenKey: "",
|
||||
TokenValue: "",
|
||||
TagNames: "",
|
||||
TagNamesCufd: "",
|
||||
QueryInsert: "",
|
||||
MsgCustomResponse: "",
|
||||
}
|
||||
|
||||
//registrar-empresa solo para registrar empresa
|
||||
cuisServiceModel.TagNames = "cuis"
|
||||
cuisServiceModel.TagNamesCufd = "cufd"
|
||||
cuisServiceModel.QueryInsert = "INSERT INTO registroEmpresa (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
cuisServiceModel.MsgCustomResponse = "Registrar Empresa"
|
||||
|
||||
procesarRegistro := procesar.NewProcesarRegistro(cuisServiceModel)
|
||||
empresaRepository := repositories.NewEmpresaRepository(cuisServiceModel)
|
||||
cuisService := services.NewCuisService(cuisServiceModel, serviceLogger)
|
||||
cuisRepository := repositories.NewCuisRepository(baseSvc, cuisService)
|
||||
cronHandler := handlers.NewCronHandler(cuisService, empresaRepository, cuisRepository, procesarRegistro)
|
||||
apiHandler := handlers.NewApiHandler(cuisService, cuisRepository, procesarRegistro)
|
||||
|
||||
// Llamar a NewObtencionCodigoService correctamente
|
||||
obtencionCodigoSvc := obtencionCodigo.NewObtencionCodigoService(
|
||||
cuisServiceModel,
|
||||
cuisService,
|
||||
cronHandler,
|
||||
apiHandler,
|
||||
)
|
||||
|
||||
switch svcCfg.Name {
|
||||
case "registrar-empresa":
|
||||
servicesList2 = append(servicesList2, obtencionCodigoSvc)
|
||||
break
|
||||
case "actividades": //todo revisar que pasa
|
||||
baseSvc.TagNames = "sincronizarActividades"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_actividades (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado de Actividades"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "sincronizar_fecha_hora": //todo revisar que pasa
|
||||
baseSvc.TagNames = "sincronizarFechaHora"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_fecha_hora (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Sincronizar la hora"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "sincronizar_mensaje_servicio": //todo revisar que pasa
|
||||
baseSvc.TagNames = "sincronizarListaMensajesServicios"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_mensaje_servicio (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Sincronizar mensaje servicio"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "leyendas_factura":
|
||||
baseSvc.TagNames = "sincronizarListaLeyendasFactura"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_leyendas_factura (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado Leyendas de Facturas"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "producto-servicio":
|
||||
baseSvc.TagNames = "sincronizarListaProductosServicios"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_productos_servicios (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado Producto Servicio"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_motivo_anulacion":
|
||||
baseSvc.TagNames = "sincronizarParametricaMotivoAnulacion"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_motivo_anulacion (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado motivo anulacion"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_documento_identidad":
|
||||
baseSvc.TagNames = "sincronizarParametricaTipoDocumentoIdentidad"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_documento_identidad (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado tipos de documento de identidad"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_documento_sector":
|
||||
baseSvc.TagNames = "sincronizarParametricaTipoDocumentoSector"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_documento_sector (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado tipos de documento sector"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_emision": //todo falta este
|
||||
baseSvc.TagNames = "sincronizarParametricaTipoEmision"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_emision (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado tipos de emision"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_actividades_documento_sector": //todo falta este
|
||||
baseSvc.TagNames = "sincronizarListaActividadesDocumentoSector"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_actividades_documento_sector (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado tipos de emision"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_metodo_pago":
|
||||
baseSvc.TagNames = "sincronizarParametricaTipoMetodoPago"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_metodo_pago (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado tipos metodo de pago"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_moneda":
|
||||
baseSvc.TagNames = "sincronizarParametricaTipoMoneda"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_moneda (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado tipos de moneda"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_punto_venta":
|
||||
baseSvc.TagNames = "sincronizarParametricaTipoPuntoVenta"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_punto_venta (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado tipos de punto de venta"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_factura": //todo este falta
|
||||
baseSvc.TagNames = "sincronizarParametricaTiposFactura"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_factura (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado tipos de factura"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_unidad_medida":
|
||||
baseSvc.TagNames = "sincronizarParametricaUnidadMedida"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_tipo_unidad_medida (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado tipos de unidad de medida"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "tipo_evento_significativos":
|
||||
baseSvc.TagNames = "sincronizarParametricaEventosSignificativos"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_evento_significativo (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado eventos significativos"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "sincronizar_tipo_habitacion":
|
||||
baseSvc.TagNames = "sincronizarParametricaTipoHabitacion"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_Tipo_Habitacion (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado eventos significativos"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
case "sincronizar_pais_origen":
|
||||
baseSvc.TagNames = "sincronizarParametricaPaisOrigen"
|
||||
baseSvc.QueryInsert = "INSERT INTO sincronizar_Pais_Origen (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
baseSvc.MsgCustomResponse = "Listado eventos significativos"
|
||||
servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
ServiceModel: baseSvc,
|
||||
})
|
||||
break
|
||||
default:
|
||||
log.Printf("Servicio desconocido: %s", svcCfg.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Si se especifica un servicio específico, filtrar la lista.
|
||||
if *serviceName != "" {
|
||||
var filtered []models.SoapService
|
||||
for _, s := range servicesList {
|
||||
if s.GetName() == *serviceName {
|
||||
filtered = append(filtered, s)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
log.Fatalf("No se encontró el servicio: %s", *serviceName)
|
||||
}
|
||||
servicesList = filtered
|
||||
log.Printf("Ejecutando solo el servicio: %s", *serviceName)
|
||||
}
|
||||
|
||||
// Crear contexto con cancelación para manejar señales de terminación.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Canal para capturar señales del sistema.
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
// Iniciar servidor HTTP si se configura puerto para API.
|
||||
var httpServer *http.Server
|
||||
if cfg.API.Port > 0 {
|
||||
httpServer = api.StartAPIServer(cfg.API.Port, servicesList, servicesList2, false)
|
||||
}
|
||||
|
||||
// Goroutine para manejar señales.
|
||||
go func() {
|
||||
sig := <-sigChan
|
||||
log.Printf("Recibida señal: %v. Cerrando...", sig)
|
||||
if notifier != nil {
|
||||
notifier.SendNotification("Servicio detenido", fmt.Sprintf("El servicio fue detenido por la señal: %v", sig))
|
||||
}
|
||||
cancel()
|
||||
if httpServer != nil {
|
||||
log.Println("Cerrando servidor HTTP...")
|
||||
shutdownCtx, cancelServer := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancelServer()
|
||||
if err := httpServer.Shutdown(shutdownCtx); err != nil {
|
||||
log.Printf("Error al cerrar servidor HTTP: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Si se debe ejecutar solo una vez.
|
||||
if *runOnce {
|
||||
errors := services.RunAllServices(ctx, servicesList, false)
|
||||
if len(errors) > 0 {
|
||||
for _, e := range errors {
|
||||
log.Printf("Error: %v", e)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Configurar primer cron
|
||||
c := cron.New(cron.WithSeconds())
|
||||
_, err = c.AddFunc(cfg.Schedule.FACTURA_SINCRONIZACION.Cron, func() {
|
||||
log.Println("Iniciando ejecución programada de servicios")
|
||||
execCtx, execCancel := context.WithCancel(ctx)
|
||||
defer execCancel()
|
||||
|
||||
errors := services.RunAllServices(execCtx, servicesList, true)
|
||||
if len(errors) > 0 {
|
||||
errMsg := "Errores durante la ejecución programada:"
|
||||
for _, err := range errors {
|
||||
errMsg += "\n- " + err.Error()
|
||||
}
|
||||
if notifier != nil {
|
||||
notifier.SendNotification("Errores en ejecución programada", errMsg)
|
||||
}
|
||||
log.Println(errMsg)
|
||||
} else {
|
||||
log.Println("Ejecución programada completada exitosamente")
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Error al programar tareas: %v", err)
|
||||
}
|
||||
|
||||
// Configurar segundo cron
|
||||
cronGetCode := cron.New(cron.WithSeconds())
|
||||
_, err = cronGetCode.AddFunc(cfg.Schedule.FACTURA_CODIGO.Cron, func() {
|
||||
log.Println("Iniciando ejecución programada para Obtener Codigos")
|
||||
execCtx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
//execCtx, execCancel := context.WithCancel(ctx)
|
||||
//defer execCancel()
|
||||
|
||||
errors := services.RunAllGetCodeServices(execCtx, servicesList2, true)
|
||||
if len(errors) > 0 {
|
||||
errMsg := "Errores durante la ejecución programada Obtener Codigos:"
|
||||
for _, err := range errors {
|
||||
errMsg += "\n- " + err.Error()
|
||||
}
|
||||
if notifier != nil {
|
||||
notifier.SendNotification("Errores en ejecución programada Obtener Codigos", errMsg)
|
||||
}
|
||||
log.Println(errMsg)
|
||||
} else {
|
||||
log.Println("Ejecución programada completada exitosamente Obtener Codigos")
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Error al programar tareas Obtener Codigos: %v", err)
|
||||
}
|
||||
|
||||
// Iniciar ambos cron
|
||||
c.Start()
|
||||
cronGetCode.Start()
|
||||
|
||||
log.Printf("Demonio de servicios iniciado. Programación: %s", cfg.Schedule.FACTURA_SINCRONIZACION.Cron)
|
||||
log.Printf("Demonio de obtención de códigos iniciado. Programación: %s", cfg.Schedule.FACTURA_CODIGO.Cron)
|
||||
|
||||
if notifier != nil {
|
||||
notifier.SendNotification("Servicios iniciados",
|
||||
fmt.Sprintf("Los servicios SOAP han sido iniciados con programación: %s", cfg.Schedule.FACTURA_SINCRONIZACION.Cron))
|
||||
}
|
||||
|
||||
// Esperar a que el contexto termine y luego detener ambos cron
|
||||
<-ctx.Done()
|
||||
c.Stop()
|
||||
cronGetCode.Stop()
|
||||
log.Println("Demonios detenidos correctamente")
|
||||
}
|
104
daemonService/configs/config.yaml
Normal file
104
daemonService/configs/config.yaml
Normal file
@ -0,0 +1,104 @@
|
||||
# Configuración del demonio SOAP
|
||||
database:
|
||||
host: localhost
|
||||
port: 5555
|
||||
user: facturacion_user
|
||||
password: facturacion_pass
|
||||
dbname: facturacion_bd
|
||||
sslmode: disable
|
||||
|
||||
soap:
|
||||
factura_sincronizacion:
|
||||
endpoint: https://pilotosiatservicios.impuestos.gob.bo/v2/FacturacionSincronizacion
|
||||
# endpoint: http://localhost:8088/mockServicioFacturacionSincronizacionSoapBinding
|
||||
timeout: 30 # segundos
|
||||
retries: 3
|
||||
factura_codigo:
|
||||
endpoint: https://pilotosiatservicios.impuestos.gob.bo/v2/FacturacionCodigos
|
||||
# endpoint: http://localhost:8088/mockServicioFacturacionCodigosSoapBinding
|
||||
timeout: 30 # segundos
|
||||
retries: 3
|
||||
|
||||
schedule:
|
||||
factura_sincronizacion:
|
||||
# cron: "0 0 */6 * * *" # Cada 6 horas
|
||||
cron: "0 */1 * * * *" # Cada 1 minutos
|
||||
factura_codigo:
|
||||
# cron: "0 0 */7 * * *" # Cada 7 horas
|
||||
cron: "0 */1 * * * *" # Cada 1 minutos
|
||||
|
||||
# Configuración de la API HTTP
|
||||
api:
|
||||
port: 9999 # Puerto para el servidor HTTP
|
||||
|
||||
# Configuración de notificaciones
|
||||
notifications:
|
||||
enabled: true
|
||||
method: "email" # Opciones: "webhook", "email", "slack"
|
||||
config:
|
||||
# email - correo
|
||||
smtp_server: "mail.myapps.bo"
|
||||
smtp_port: "465"
|
||||
username: "keycloaktest@myapps.bo"
|
||||
password: "%l12k*89f1w:"
|
||||
from_email: "keycloaktest@myapps.bo"
|
||||
to_email: "ariel.anivarro@myapps.com.bo"
|
||||
|
||||
services:
|
||||
- name: registrar-empresa
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: actividades
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: sincronizar_fecha_hora
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: sincronizar_mensaje_servicio
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: leyendas_factura
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: producto-servicio
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_motivo_anulacion
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_actividades_documento_sector
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_documento_identidad
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_documento_sector
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_emision
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_metodo_pago
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_moneda
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_punto_venta
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_factura
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_unidad_medida
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: tipo_evento_significativos
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: sincronizar_tipo_habitacion
|
||||
enabled: true
|
||||
concurrency: 1
|
||||
- name: sincronizar_pais_origen
|
||||
enabled: true
|
||||
concurrency: 1
|
BIN
daemonService/daemon-services
Normal file
BIN
daemonService/daemon-services
Normal file
Binary file not shown.
10
daemonService/go.mod
Normal file
10
daemonService/go.mod
Normal file
@ -0,0 +1,10 @@
|
||||
module daemonService
|
||||
|
||||
go 1.21.6
|
||||
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
10
daemonService/go.sum
Normal file
10
daemonService/go.sum
Normal file
@ -0,0 +1,10 @@
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
196
daemonService/internal/api/router.go.BACKUP
Normal file
196
daemonService/internal/api/router.go.BACKUP
Normal file
@ -0,0 +1,196 @@
|
||||
package api
|
||||
|
||||
//import (
|
||||
// "context"
|
||||
// "daemonService/internal/models"
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "io"
|
||||
// "log"
|
||||
// "net/http"
|
||||
// "time"
|
||||
//
|
||||
// "daemonService/internal/services"
|
||||
//
|
||||
// "github.com/gorilla/mux"
|
||||
//)
|
||||
//
|
||||
//// APIResponse estructura la respuesta común del API.
|
||||
//type APIResponse struct {
|
||||
// Success bool `json:"success"`
|
||||
// Message string `json:"message"`
|
||||
// Data interface{} `json:"data,omitempty"`
|
||||
// Error string `json:"error,omitempty"`
|
||||
//}
|
||||
//
|
||||
//// setupAPIHandlers configura los endpoints del API HTTP.
|
||||
//func setupAPIHandlers(router *mux.Router, service []models.SoapService, isCron bool) {
|
||||
// // Lista de servicios disponibles
|
||||
// router.HandleFunc("/api/services", func(w http.ResponseWriter, r *http.Request) {
|
||||
// serviceNames := make([]string, len(service))
|
||||
// for i, svc := range service {
|
||||
// serviceNames[i] = svc.GetName()
|
||||
// }
|
||||
//
|
||||
// response := APIResponse{
|
||||
// Success: true,
|
||||
// Message: "Lista de servicios disponibles",
|
||||
// Data: serviceNames,
|
||||
// }
|
||||
// w.Header().Set("Content-Type", "application/json")
|
||||
// json.NewEncoder(w).Encode(response)
|
||||
// }).Methods("GET")
|
||||
//
|
||||
// // Ejecutar todos los servicios
|
||||
// router.HandleFunc("/api/services/run-all", func(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute)
|
||||
// defer cancel()
|
||||
//
|
||||
// go func() {
|
||||
// errors := services.RunAllServices(ctx, service, isCron) //ariel agrego:request
|
||||
// log.Printf("Ejecución de todos los servicios completada con %d errores", len(errors))
|
||||
// }()
|
||||
//
|
||||
// response := APIResponse{
|
||||
// Success: true,
|
||||
// Message: "Ejecución de todos los servicios iniciada",
|
||||
// }
|
||||
// w.Header().Set("Content-Type", "application/json")
|
||||
// json.NewEncoder(w).Encode(response)
|
||||
// }).Methods("POST")
|
||||
//
|
||||
// // Ejecutar un servicio específico
|
||||
// router.HandleFunc("/api/services/{name}", func(w http.ResponseWriter, r *http.Request) {
|
||||
// vars := mux.Vars(r)
|
||||
//
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// // Es importante cerrar el body después de leerlo para liberar recursos.
|
||||
// defer r.Body.Close()
|
||||
// // Leemos el contenido del body.
|
||||
// body, err := io.ReadAll(r.Body)
|
||||
// if err != nil {
|
||||
// http.Error(w, "Error leyendo el body", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// // Si quieres convertirlo a string:
|
||||
// bodyStr := string(body)
|
||||
// fmt.Println("Body de la petición:", bodyStr)
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// serviceName := vars["name"]
|
||||
//
|
||||
// ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute)
|
||||
// defer cancel()
|
||||
//
|
||||
// var encontrado bool
|
||||
// for _, svc := range service {
|
||||
// if svc.GetName() == serviceName {
|
||||
// encontrado = true
|
||||
// go func() {
|
||||
// // if err := svc.Execute(ctx); err != nil {
|
||||
// // log.Printf("Error al ejecutar servicio %s: %v", serviceName, err)
|
||||
// // } else {
|
||||
// // log.Printf("Servicio %s ejecutado correctamente", serviceName)
|
||||
// // }
|
||||
// if err := svc.Execute(ctx, r, isCron); err != nil { //ariel agrego:request
|
||||
// log.Printf("Error al ejecutar servicio %s: %v", serviceName, err) ///modificado
|
||||
// } else {
|
||||
// log.Printf("Servicio %s ejecutado correctamente", serviceName)
|
||||
// }
|
||||
// }()
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if !encontrado {
|
||||
// w.WriteHeader(http.StatusNotFound)
|
||||
// response := APIResponse{
|
||||
// Success: false,
|
||||
// Error: fmt.Sprintf("Servicio no encontrado: %s", serviceName),
|
||||
// }
|
||||
// w.Header().Set("Content-Type", "application/json")
|
||||
// json.NewEncoder(w).Encode(response)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// response := APIResponse{
|
||||
// Success: true,
|
||||
// Message: fmt.Sprintf("Ejecución del servicio %s iniciada", serviceName),
|
||||
// }
|
||||
// w.Header().Set("Content-Type", "application/json")
|
||||
// json.NewEncoder(w).Encode(response)
|
||||
// }).Methods("POST")
|
||||
//
|
||||
// // Health check
|
||||
// router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
// response := APIResponse{
|
||||
// Success: true,
|
||||
// Message: "Servicio activo",
|
||||
// Data: map[string]interface{}{
|
||||
// "status": "healthy",
|
||||
// "timestamp": time.Now().Format(time.RFC3339),
|
||||
// },
|
||||
// }
|
||||
// w.Header().Set("Content-Type", "application/json")
|
||||
// json.NewEncoder(w).Encode(response)
|
||||
// }).Methods("GET")
|
||||
//}
|
||||
//
|
||||
//// StartAPIServer inicia el servidor HTTP con la configuración del API.
|
||||
//func StartAPIServer(port int, services []models.SoapService, isCron bool) *http.Server {
|
||||
// router := mux.NewRouter()
|
||||
// setupAPIHandlers(router, services, isCron)
|
||||
//
|
||||
// // Middleware para logging
|
||||
// router.Use(func(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// start := time.Now()
|
||||
// log.Printf("API Request: %s %s", r.Method, r.URL.Path)
|
||||
// next.ServeHTTP(w, r)
|
||||
// log.Printf("API Request: %s %s completada en %v", r.Method, r.URL.Path, time.Since(start))
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// srv := &http.Server{
|
||||
// Addr: fmt.Sprintf(":%d", port),
|
||||
// Handler: router,
|
||||
// ReadTimeout: 15 * time.Second,
|
||||
// WriteTimeout: 15 * time.Second,
|
||||
// IdleTimeout: 60 * time.Second,
|
||||
// }
|
||||
//
|
||||
// go func() {
|
||||
// log.Printf("API HTTP iniciada en puerto %d", port)
|
||||
// if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
// log.Fatalf("Error en servidor HTTP: %v", err)
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// return srv
|
||||
//}
|
||||
|
||||
// // runAllServices ejecuta todos los servicios de manera concurrente.
|
||||
// func runAllServices(ctx context.Context, services []services.SoapService) []error {
|
||||
// var wg sync.WaitGroup
|
||||
// var mu sync.Mutex
|
||||
// var errors []error
|
||||
|
||||
// for _, svc := range services {
|
||||
// s := svc
|
||||
// wg.Add(1)
|
||||
// go func() {
|
||||
// defer wg.Done()
|
||||
// serviceCtx, cancel := context.WithCancel(ctx)
|
||||
// defer cancel()
|
||||
// // if err := s.Execute(serviceCtx); err != nil {
|
||||
// if err := s.Execute(serviceCtx, nil); err != nil { //ariel agrego:request
|
||||
// mu.Lock()
|
||||
// errors = append(errors, err)
|
||||
// mu.Unlock()
|
||||
// }
|
||||
// }()
|
||||
// }
|
||||
// wg.Wait()
|
||||
// log.Println("Todos los servicios han sido ejecutados")
|
||||
// return errors
|
||||
// }
|
87
daemonService/internal/api/routerApi.go
Normal file
87
daemonService/internal/api/routerApi.go
Normal file
@ -0,0 +1,87 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"daemonService/internal/handlers"
|
||||
"daemonService/internal/models"
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
router *mux.Router
|
||||
services []models.SoapService
|
||||
servicesCuis []obtencionCodigos.CuisService
|
||||
isCron bool
|
||||
port int
|
||||
}
|
||||
|
||||
func NewServer(port int, services []models.SoapService, servicesCuis []obtencionCodigos.CuisService, isCron bool) *Server {
|
||||
s := &Server{
|
||||
router: mux.NewRouter(),
|
||||
services: services,
|
||||
servicesCuis: servicesCuis,
|
||||
isCron: isCron,
|
||||
port: port,
|
||||
}
|
||||
|
||||
s.setupRoutes()
|
||||
s.setupMiddleware()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) setupRoutes() {
|
||||
// Register service-related routes
|
||||
serviceHandler := handlers.NewServiceHandler(s.services, s.servicesCuis, s.isCron)
|
||||
s.router.HandleFunc("/api/services", serviceHandler.ListServices).Methods("GET")
|
||||
s.router.HandleFunc("/api/services/run-all", serviceHandler.RunAllServices).Methods("GET")
|
||||
s.router.HandleFunc("/api/services/{name}", serviceHandler.RunService).Methods("GET")
|
||||
//s.router.HandleFunc("/api/services/run-all", serviceHandler.RunAllServices).Methods("POST")
|
||||
//s.router.HandleFunc("/api/services/{name}", serviceHandler.RunService).Methods("POST")
|
||||
|
||||
//serviceCuisHandler := obtencionCodigo.HandlerServiceCuis(s.services, s.isCron)
|
||||
s.router.HandleFunc("/api/services/get-code/{obtenerCodigo}", serviceHandler.RunGetCodeService).Methods("POST")
|
||||
|
||||
healthHandler := handlers.NewHealthHandler()
|
||||
s.router.HandleFunc("/api/health", healthHandler.Check).Methods("GET")
|
||||
}
|
||||
|
||||
func (s *Server) setupMiddleware() {
|
||||
s.router.Use(LoggingMiddleware)
|
||||
}
|
||||
|
||||
func (s *Server) Start() *http.Server {
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", s.port),
|
||||
Handler: s.router,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Printf("API HTTP started on port %d", s.port)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("HTTP server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
func LoggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
log.Printf("API Request: %s %s", r.Method, r.URL.Path)
|
||||
next.ServeHTTP(w, r)
|
||||
log.Printf("API Request: %s %s completed in %v", r.Method, r.URL.Path, time.Since(start))
|
||||
})
|
||||
}
|
||||
|
||||
func StartAPIServer(port int, services []models.SoapService, serviceCuis []obtencionCodigos.CuisService, isCron bool) *http.Server {
|
||||
server := NewServer(port, services, serviceCuis, isCron)
|
||||
return server.Start()
|
||||
}
|
127
daemonService/internal/config/config.go
Normal file
127
daemonService/internal/config/config.go
Normal file
@ -0,0 +1,127 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Config estructura la configuración completa del demonio.
|
||||
type Config struct {
|
||||
Database struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
DBName string `yaml:"dbname"`
|
||||
SSLMode string `yaml:"sslmode"`
|
||||
} `yaml:"database"`
|
||||
SOAP struct {
|
||||
APIKey string `yaml:"apikey"`
|
||||
FACTURA_SINCRONIZACION struct {
|
||||
Endpoint string `yaml:"endpoint"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
Retries int `yaml:"retries"`
|
||||
}
|
||||
FACTURA_CODIGO struct {
|
||||
Endpoint string `yaml:"endpoint"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
Retries int `yaml:"retries"`
|
||||
}
|
||||
} `yaml:"soap"`
|
||||
Schedule struct {
|
||||
FACTURA_SINCRONIZACION struct {
|
||||
Cron string `yaml:"cron"`
|
||||
}
|
||||
FACTURA_CODIGO struct {
|
||||
Cron string `yaml:"cron"`
|
||||
}
|
||||
} `yaml:"schedule"`
|
||||
API struct {
|
||||
Port int `yaml:"port"`
|
||||
} `yaml:"api"`
|
||||
Notifications struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Method string `yaml:"method"` // "email", "webhook", "slack", etc.
|
||||
Config map[string]string `yaml:"config"`
|
||||
} `yaml:"notifications"`
|
||||
Services []struct {
|
||||
Name string `yaml:"name"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Concurrency int `yaml:"concurrency"`
|
||||
} `yaml:"services"`
|
||||
}
|
||||
|
||||
// LoadConfig carga la configuración desde el archivo YAML.
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var cfg Config
|
||||
decoder := yaml.NewDecoder(f)
|
||||
if err := decoder.Decode(&cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// package config
|
||||
|
||||
// import (
|
||||
// "os"
|
||||
|
||||
// "gopkg.in/yaml.v2"
|
||||
// )
|
||||
|
||||
// // Config estructura global de configuración.
|
||||
// type Config struct {
|
||||
// Database struct {
|
||||
// Host string `yaml:"host"`
|
||||
// Port int `yaml:"port"`
|
||||
// User string `yaml:"user"`
|
||||
// Password string `yaml:"password"`
|
||||
// DBName string `yaml:"dbname"`
|
||||
// SSLMode string `yaml:"sslmode"`
|
||||
// } `yaml:"database"`
|
||||
// SOAP struct {
|
||||
// Endpoint string `yaml:"endpoint"`
|
||||
// Timeout int `yaml:"timeout"`
|
||||
// Retries int `yaml:"retries"`
|
||||
// } `yaml:"soap"`
|
||||
// Schedule struct {
|
||||
// Cron string `yaml:"cron"`
|
||||
// } `yaml:"schedule"`
|
||||
// API struct {
|
||||
// Port int `yaml:"port"`
|
||||
// } `yaml:"api"`
|
||||
// Notifications struct {
|
||||
// Enabled bool `yaml:"enabled"`
|
||||
// Method string `yaml:"method"` // "email", "webhook", "slack", etc.
|
||||
// Config map[string]string `yaml:"config"`
|
||||
// } `yaml:"notifications"`
|
||||
// Services []struct {
|
||||
// Name string `yaml:"name"`
|
||||
// Enabled bool `yaml:"enabled"`
|
||||
// Concurrency int `yaml:"concurrency"`
|
||||
// } `yaml:"services"`
|
||||
// }
|
||||
|
||||
// // Load carga la configuración a partir de un archivo YAML.
|
||||
// func Load(configFile string) (*Config, error) {
|
||||
// f, err := os.Open(configFile)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer f.Close()
|
||||
|
||||
// var cfg Config
|
||||
// decoder := yaml.NewDecoder(f)
|
||||
// if err := decoder.Decode(&cfg); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return &cfg, nil
|
||||
// }
|
124
daemonService/internal/controllers/responseController.go
Normal file
124
daemonService/internal/controllers/responseController.go
Normal file
@ -0,0 +1,124 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"daemonService/internal/models"
|
||||
"daemonService/internal/models/obtencionCodigos/response"
|
||||
"daemonService/internal/models/sincronizacionDatos/bodyGeneric"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func RespondWithJSON[T any](w http.ResponseWriter, statusCode int, payload bodyGeneric.CustomResponse[T]) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
||||
http.Error(w, "Error encoding JSON response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func RespondWithJSONDEMO(w http.ResponseWriter, statusCode int, payload interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
||||
http.Error(w, "Error encoding JSON response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func RespondWithError(w http.ResponseWriter, statusCode int, errMsg string) {
|
||||
RespondWithJSON(w, statusCode, bodyGeneric.CustomResponse[any]{
|
||||
Success: false,
|
||||
Status: statusCode,
|
||||
Message: errMsg,
|
||||
Data: nil,
|
||||
})
|
||||
}
|
||||
|
||||
func RespondWithSuccess(w http.ResponseWriter, statusCode int, message string, data interface{}) {
|
||||
|
||||
////df := data.(models.APIResponse)
|
||||
////
|
||||
////println(datas)
|
||||
//
|
||||
//// Ejemplo: si data viene como []Moneda
|
||||
//if monedas, ok := data.(models.APIResponse); ok {
|
||||
// //primera := monedas[0]
|
||||
// fmt.Printf("Primera moneda: %+v\n", monedas)
|
||||
//} else {
|
||||
// // Quizá data es []interface{} (lo típico tras un Unmarshal genérico)
|
||||
// if arr, ok2 := data.([]interface{}); ok2 {
|
||||
// // arr[0] es un interface{} que suele ser un map[string]interface{}
|
||||
// if m0, ok3 := arr[0].(map[string]interface{}); ok3 {
|
||||
// fmt.Println("Código:", m0["codigo"])
|
||||
// fmt.Println("Descripción:", m0["descripcion"])
|
||||
// }
|
||||
// } else {
|
||||
// // Tipo inesperado
|
||||
// http.Error(w, "tipo de dato no soportado en RespondWithSuccess", 500)
|
||||
// return
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//RespondWithJSONDEMO(w, statusCode, data)
|
||||
|
||||
RespondWithJSON(w, statusCode, bodyGeneric.CustomResponse[any]{
|
||||
Success: true,
|
||||
Status: statusCode,
|
||||
Message: message,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// nuevo repsonse --------------------------------------------------------------------------------------------
|
||||
func RespondWithJSONDemo(w http.ResponseWriter, statusCode int, payload models.APIResponseCuis) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
||||
http.Error(w, "Error encoding JSON response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertToCuisMinimal convierte diferentes tipos de datos a CuisMinimal
|
||||
func ConvertToCuisStruct(data interface{}) (models.CuisMinimal, error) {
|
||||
var cuisMinimal models.CuisMinimal
|
||||
|
||||
switch typedData := data.(type) {
|
||||
case response.SoapBodyCuis:
|
||||
cuisMinimal = models.CuisMinimal{
|
||||
Codigo: typedData.Response.Respuesta.Codigo,
|
||||
FechaVigencia: typedData.Response.Respuesta.FechaVigencia,
|
||||
Transaccion: typedData.Response.Respuesta.Transaccion,
|
||||
}
|
||||
case models.CuisMinimal:
|
||||
cuisMinimal = typedData
|
||||
default:
|
||||
return models.CuisMinimal{}, data.(error)
|
||||
//return CuisMinimal{}, fmt.Errorf("tipo de datos incompatible")
|
||||
}
|
||||
return cuisMinimal, nil
|
||||
}
|
||||
|
||||
// responde con éxito usando datos de tipo CuisMinimal
|
||||
func RespondCuisWithSuccess(w http.ResponseWriter, statusCode int, message string, data interface{}) {
|
||||
|
||||
cuisMinimal, err := ConvertToCuisStruct(data)
|
||||
if err != nil {
|
||||
var apiErr models.ApiErrorCustom
|
||||
if errors.As(err.(error), &apiErr) {
|
||||
RespondWithError(w, apiErr.Status, apiErr.Message)
|
||||
return
|
||||
} else {
|
||||
RespondWithError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
RespondWithJSONDemo(w, statusCode, models.APIResponseCuis{
|
||||
Success: true,
|
||||
Message: message,
|
||||
Data: cuisMinimal,
|
||||
})
|
||||
}
|
127
daemonService/internal/database/db.go
Normal file
127
daemonService/internal/database/db.go
Normal file
@ -0,0 +1,127 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"daemonService/internal/config"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
// InitDB inicializa la conexión a la base de datos y crea las tablas si no existen.
|
||||
func InitDB(cfg config.Config) (*sql.DB, error) {
|
||||
connStr := fmt.Sprintf(
|
||||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
cfg.Database.Host,
|
||||
cfg.Database.Port,
|
||||
cfg.Database.User,
|
||||
cfg.Database.Password,
|
||||
cfg.Database.DBName,
|
||||
cfg.Database.SSLMode,
|
||||
)
|
||||
|
||||
db, err := sql.Open("postgres", connStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verificar la conexión
|
||||
if err = db.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Crear tabla para tipos de documento de identidad si no existe
|
||||
//_, err = db.Exec(`
|
||||
// CREATE TABLE IF NOT EXISTS tipo_documento_identidad (
|
||||
// codigo VARCHAR(10) PRIMARY KEY,
|
||||
// descripcion TEXT NOT NULL,
|
||||
// fecha_actualizacion TIMESTAMP NOT NULL
|
||||
// )
|
||||
//`)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
|
||||
// Crear tabla para el log de sincronización
|
||||
//_, err = db.Exec(`
|
||||
// CREATE TABLE IF NOT EXISTS log_sincronizacion (
|
||||
// id SERIAL PRIMARY KEY,
|
||||
// servicio VARCHAR(50) NOT NULL,
|
||||
// req_soap TEXT,
|
||||
// resp_soap TEXT,
|
||||
// req_json TEXT,
|
||||
// resp_json TEXT,
|
||||
// fecha TIMESTAMP NOT NULL
|
||||
// )
|
||||
//`)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// package db
|
||||
|
||||
// import (
|
||||
// "database/sql"
|
||||
// "fmt"
|
||||
|
||||
// "daemonService/internal/config"
|
||||
|
||||
// _ "github.com/lib/pq"
|
||||
// )
|
||||
|
||||
// // Init inicializa la conexión a la base de datos y crea las tablas necesarias.
|
||||
// func Init(cfg *config.Config) (*sql.DB, error) {
|
||||
// connStr := fmt.Sprintf(
|
||||
// "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
// cfg.Database.Host,
|
||||
// cfg.Database.Port,
|
||||
// cfg.Database.User,
|
||||
// cfg.Database.Password,
|
||||
// cfg.Database.DBName,
|
||||
// cfg.Database.SSLMode,
|
||||
// )
|
||||
|
||||
// db, err := sql.Open("postgres", connStr)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// // Verificar la conexión
|
||||
// if err = db.Ping(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// // Crear tabla para tipo de documento de identidad
|
||||
// _, err = db.Exec(`
|
||||
// CREATE TABLE IF NOT EXISTS tipo_documento_identidad (
|
||||
// codigo VARCHAR(10) PRIMARY KEY,
|
||||
// descripcion TEXT NOT NULL,
|
||||
// fecha_actualizacion TIMESTAMP NOT NULL
|
||||
// )
|
||||
// `)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// // Crear tabla de log para auditoría
|
||||
// _, err = db.Exec(`
|
||||
// CREATE TABLE IF NOT EXISTS log_sincronizacion (
|
||||
// id SERIAL PRIMARY KEY,
|
||||
// servicio VARCHAR(50) NOT NULL,
|
||||
// req_soap TEXT,
|
||||
// resp_soap TEXT,
|
||||
// req_json TEXT,
|
||||
// resp_json TEXT,
|
||||
// fecha TIMESTAMP NOT NULL
|
||||
// )
|
||||
// `)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// return db, nil
|
||||
// }
|
101
daemonService/internal/handlers/api_handler.go
Normal file
101
daemonService/internal/handlers/api_handler.go
Normal file
@ -0,0 +1,101 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/models/obtencionCodigos/request"
|
||||
"daemonService/internal/repositories"
|
||||
serv "daemonService/internal/services"
|
||||
"daemonService/internal/services/procesar"
|
||||
"daemonService/internal/utils"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type APIHandler struct {
|
||||
CuisService *serv.CuisService
|
||||
CuisRepository *repositories.CuisRepository
|
||||
ProcesarRegistro *procesar.ProcesarRegistro
|
||||
}
|
||||
|
||||
func NewApiHandler(cuisService *serv.CuisService, cuisRepo *repositories.CuisRepository, procesarRegistro *procesar.ProcesarRegistro) *APIHandler {
|
||||
return &APIHandler{CuisService: cuisService, CuisRepository: cuisRepo, ProcesarRegistro: procesarRegistro}
|
||||
}
|
||||
|
||||
// ParseCuisRequest parsea la solicitud de CUIS desde el body del request
|
||||
func (h *APIHandler) ParseCuisRequest(w http.ResponseWriter, req *http.Request) (request.SolicitudCuisCufd, error) {
|
||||
var cuisRequest request.SolicitudCuisCufd
|
||||
|
||||
// Limitar el tamaño del body para prevenir ataques DoS
|
||||
req.Body = http.MaxBytesReader(w, req.Body, utils.MaxBodySize)
|
||||
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
decoder.DisallowUnknownFields() // Rechazar campos desconocidos
|
||||
|
||||
if err := decoder.Decode(&cuisRequest); err != nil {
|
||||
return cuisRequest, fmt.Errorf("error al decodificar JSON: %v", err)
|
||||
}
|
||||
|
||||
return cuisRequest, nil
|
||||
}
|
||||
|
||||
// maneja las solicitudes API
|
||||
func (h *APIHandler) HandleAPIRequest(ctx context.Context, w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
// Parsear la solicitud
|
||||
cuisRequest, err := h.ParseCuisRequest(w, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validar la solicitud
|
||||
if err := h.ValidateCuisRequest(cuisRequest); err != nil {
|
||||
return nil, fmt.Errorf("datos inválidos: %v", err)
|
||||
}
|
||||
|
||||
// Procesar la solicitud Cuis
|
||||
respCuis, _, err := h.ProcesarRegistro.CodigoProcesarRegistro(cuisRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(utils.LogErrorProcessing, cuisRequest.CodigoSistema, err)
|
||||
}
|
||||
|
||||
// Parsear respuesta SOAP
|
||||
codCuis, fechaVigencia, transaccion, err := utils.ParseSoapCuisResponse(respCuis)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error al parsear respuesta CUIS SOAP: %v", err)
|
||||
}
|
||||
|
||||
// Procesamos la solicitud Cufd
|
||||
cuisRequest.Cuis = codCuis
|
||||
_, codCufd, err := h.ProcesarRegistro.CodigoProcesarRegistro(cuisRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(utils.LogErrorProcessing, cuisRequest.CodigoSistema, err)
|
||||
}
|
||||
|
||||
fmt.Println("codigo CUFD: %v", codCufd)
|
||||
|
||||
cufdData, err := utils.ParseSoapCufdResponse(codCufd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error al parsear respuesta CUFD SOAP: %v", err)
|
||||
}
|
||||
|
||||
// Crear nuevo registro
|
||||
if err := h.CuisRepository.CrearNuevoRegistro(ctx, cuisRequest, codCuis, fechaVigencia, transaccion, cufdData); err != nil {
|
||||
//return nil, fmt.Errorf("error creando nuevo registro: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return respCuis, nil
|
||||
}
|
||||
|
||||
// valida los datos de la solicitud CUIS
|
||||
func (s *APIHandler) ValidateCuisRequest(request request.SolicitudCuisCufd) error {
|
||||
if request.CodigoAmbiente == "" ||
|
||||
request.CodigoModalidad == "" ||
|
||||
request.CodigoPuntoVenta == "" ||
|
||||
request.CodigoSistema == "" ||
|
||||
request.CodigoSucursal == "" ||
|
||||
request.Nit == "" {
|
||||
return utils.ErrAllFieldsRequired
|
||||
}
|
||||
return nil
|
||||
}
|
165
daemonService/internal/handlers/cron_handler.go
Normal file
165
daemonService/internal/handlers/cron_handler.go
Normal file
@ -0,0 +1,165 @@
|
||||
// internal/handlers/cron_handler.go
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/repositories"
|
||||
service "daemonService/internal/services"
|
||||
"daemonService/internal/services/procesar"
|
||||
"daemonService/internal/utils"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type CronHandler struct {
|
||||
CuisService *service.CuisService
|
||||
EmpresaRepo *repositories.EmpresaRepository
|
||||
CuisRespository *repositories.CuisRepository
|
||||
ProcesarRegistro *procesar.ProcesarRegistro
|
||||
}
|
||||
|
||||
// Ahora recibimos también la instancia de CuisService
|
||||
func NewCronHandler(
|
||||
cuisService *service.CuisService,
|
||||
empresaRepo *repositories.EmpresaRepository,
|
||||
cuisRespository *repositories.CuisRepository,
|
||||
procesarRegistro *procesar.ProcesarRegistro,
|
||||
) *CronHandler {
|
||||
return &CronHandler{
|
||||
CuisService: cuisService,
|
||||
EmpresaRepo: empresaRepo,
|
||||
CuisRespository: cuisRespository,
|
||||
ProcesarRegistro: procesarRegistro,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CronHandler) ProcessCronMode(dbCtx context.Context) (interface{}, error) {
|
||||
empresasConCuis, err := s.EmpresaRepo.GetEmpresasConCuisMinimal(dbCtx)
|
||||
if err != nil {
|
||||
return nil, s.CuisService.LogError("Error al obtener empresas con CUIS: %v", err)
|
||||
}
|
||||
|
||||
var lastResponse interface{}
|
||||
for _, ec := range empresasConCuis {
|
||||
s.CuisService.LogMessage("Procesando empresa ID: %d", ec.ID)
|
||||
|
||||
if len(ec.Cuis) == 0 {
|
||||
s.CuisService.LogMessage("Empresa ID %d sin CUIS registrados", ec.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range ec.Cuis {
|
||||
s.CuisService.LogMessage("Procesando CUIS ID: %d", c.Cuis_id)
|
||||
|
||||
cuisRequest := s.CuisService.BuildCuisRequestFromEmpresa(ec)
|
||||
respCuis, _, err := s.ProcesarRegistro.CodigoProcesarRegistro(cuisRequest)
|
||||
if err != nil {
|
||||
s.CuisService.LogError(utils.LogErrorProcessing, err, cuisRequest.CodigoSistema)
|
||||
continue
|
||||
}
|
||||
|
||||
// Parsear respuesta SOAP
|
||||
codCuis, fechaVigencia, transaccion, err := utils.ParseSoapCuisResponse(respCuis)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error al parsear respuesta CUIS SOAP: %v", err)
|
||||
}
|
||||
|
||||
// Procesamos la solicitud Cufd
|
||||
cuisRequest.Cuis = codCuis
|
||||
_, codCufd, err := s.ProcesarRegistro.CodigoProcesarRegistro(cuisRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(utils.LogErrorProcessing, cuisRequest.CodigoSistema, err)
|
||||
}
|
||||
|
||||
fmt.Println("codigo CUFD: %v", codCufd)
|
||||
|
||||
cufdData, err := utils.ParseSoapCufdResponse(codCufd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error al parsear respuesta CUFD SOAP: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("codigo CUFD: %v", cufdData)
|
||||
|
||||
//if err := s.CuisRespository.ActualizarRegistro(dbCtx, ec.ID, c.Cuis_id, cuisRequest, respCuis); err != nil {
|
||||
// s.CuisService.LogError("Error actualizando registro: %v", err)
|
||||
// continue
|
||||
//}
|
||||
|
||||
// Crear nuevo registro
|
||||
if err := s.CuisRespository.CrearNuevoRegistro(dbCtx, cuisRequest, codCuis, fechaVigencia, transaccion, cufdData); err != nil {
|
||||
//return nil, fmt.Errorf("error creando nuevo registro: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lastResponse = respCuis
|
||||
}
|
||||
}
|
||||
|
||||
return lastResponse, nil
|
||||
}
|
||||
|
||||
//package handlers
|
||||
//
|
||||
//import (
|
||||
// "context"
|
||||
// "daemonService/internal/repositories"
|
||||
// service "daemonService/internal/services"
|
||||
// "daemonService/internal/services/procesar"
|
||||
// "daemonService/internal/utils"
|
||||
//)
|
||||
//
|
||||
//type CronHandler struct {
|
||||
// CuisService *service.CuisService
|
||||
// EmpresaRepo *repositories.EmpresaRepository
|
||||
// CuisRespository *repositories.CuisRepository
|
||||
// ProcesarRegistro *procesar.ProcesarRegistro
|
||||
//}
|
||||
//
|
||||
//// creamos una instancia
|
||||
//func NewCronHandler(
|
||||
// empresaRepo *repositories.EmpresaRepository, cuisRespository *repositories.CuisRepository,
|
||||
// procesarRegistro *procesar.ProcesarRegistro,
|
||||
//) *CronHandler {
|
||||
// return &CronHandler{
|
||||
// EmpresaRepo: empresaRepo, CuisRespository: cuisRespository,
|
||||
// ProcesarRegistro: procesarRegistro,
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// procesa la ejecución en modo cron
|
||||
//func (s *CronHandler) ProcessCronMode(dbCtx context.Context) (interface{}, error) {
|
||||
// empresasConCuis, err := s.EmpresaRepo.GetEmpresasConCuisMinimal(dbCtx)
|
||||
// if err != nil {
|
||||
// return nil, s.CuisService.LogError("Error al obtener empresas con CUIS: %v", err)
|
||||
// }
|
||||
//
|
||||
// var lastResponse interface{}
|
||||
// for _, ec := range empresasConCuis {
|
||||
// s.CuisService.LogMessage("Procesando empresa ID: %d", ec.ID)
|
||||
//
|
||||
// if len(ec.Cuis) == 0 {
|
||||
// s.CuisService.LogMessage("Empresa ID %d sin CUIS registrados", ec.ID)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// for _, c := range ec.Cuis {
|
||||
// s.CuisService.LogMessage("Procesando CUIS ID: %d", c.Cuis_id)
|
||||
//
|
||||
// cuisRequest := s.CuisService.BuildCuisRequestFromEmpresa(ec)
|
||||
// respCuis, err := s.ProcesarRegistro.CodigoProcesarRegistro(cuisRequest, 0)
|
||||
//
|
||||
// if err != nil {
|
||||
// s.CuisService.LogError(utils.LogErrorProcessing, err, cuisRequest.CodigoSistema)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// if err := s.CuisRespository.ActualizarRegistro(dbCtx, ec.ID, c.Cuis_id, cuisRequest, respCuis); err != nil {
|
||||
// s.CuisService.LogError("Error actualizando registro: %v", err)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// lastResponse = respCuis
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return lastResponse, nil
|
||||
//}
|
29
daemonService/internal/handlers/handleError.go
Normal file
29
daemonService/internal/handlers/handleError.go
Normal file
@ -0,0 +1,29 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"daemonService/internal/models"
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func HandleError(s *models.ServiceModel, format string, args ...interface{}) error {
|
||||
errMsg := fmt.Sprintf(format, args...)
|
||||
if s.NotificationSender != nil {
|
||||
s.NotificationSender.SendNotification(
|
||||
fmt.Sprintf("Error en servicio %s", s.Name),
|
||||
errMsg,
|
||||
)
|
||||
}
|
||||
return fmt.Errorf(errMsg)
|
||||
}
|
||||
|
||||
func HandleGetCodeError(s *obtencionCodigos.CuisServiceModel, format string, args ...interface{}) error {
|
||||
errMsg := fmt.Sprintf(format, args...)
|
||||
if s.NotificationSender != nil {
|
||||
s.NotificationSender.SendNotification(
|
||||
fmt.Sprintf("Error en servicio %s", s.Name),
|
||||
errMsg,
|
||||
)
|
||||
}
|
||||
return fmt.Errorf(errMsg)
|
||||
}
|
22
daemonService/internal/handlers/healthHandler.go
Normal file
22
daemonService/internal/handlers/healthHandler.go
Normal file
@ -0,0 +1,22 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"daemonService/internal/controllers"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HealthHandler struct{}
|
||||
|
||||
func NewHealthHandler() *HealthHandler {
|
||||
return &HealthHandler{}
|
||||
}
|
||||
|
||||
func (h *HealthHandler) Check(w http.ResponseWriter, r *http.Request) {
|
||||
healthData := map[string]interface{}{
|
||||
"status": "healthy",
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
controllers.RespondWithSuccess(w, http.StatusOK, "El servicio está activo.", healthData)
|
||||
}
|
157
daemonService/internal/handlers/serviceHandler.go
Normal file
157
daemonService/internal/handlers/serviceHandler.go
Normal file
@ -0,0 +1,157 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"daemonService/internal/controllers"
|
||||
"daemonService/internal/models"
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
"daemonService/internal/services"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type ServiceHandler struct {
|
||||
services []models.SoapService
|
||||
serviceCuis []obtencionCodigos.CuisService
|
||||
isCron bool
|
||||
}
|
||||
|
||||
func NewServiceHandler(services []models.SoapService, serviceCuis []obtencionCodigos.CuisService, isCron bool) *ServiceHandler {
|
||||
return &ServiceHandler{
|
||||
services: services,
|
||||
serviceCuis: serviceCuis,
|
||||
isCron: isCron,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) ListServices(w http.ResponseWriter, r *http.Request) {
|
||||
serviceNames := make([]string, len(h.services))
|
||||
for i, svc := range h.services {
|
||||
serviceNames[i] = svc.GetName()
|
||||
}
|
||||
|
||||
controllers.RespondWithSuccess(w, http.StatusOK, "Lista de servicios disponibles", serviceNames)
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) RunAllServices(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
errors := services.RunAllServices(ctx, h.services, h.isCron)
|
||||
log.Printf("All services execution completed with %d errors", len(errors))
|
||||
}()
|
||||
|
||||
controllers.RespondWithSuccess(w, http.StatusAccepted, "Se inició la ejecución de todos los servicios.", nil)
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) RunService(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
serviceName := vars["name"]
|
||||
|
||||
bodyStr, err := h.readRequestBody(r)
|
||||
if err != nil {
|
||||
controllers.RespondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Error al leer el request body: %v", err))
|
||||
return
|
||||
}
|
||||
fmt.Println("Request body:", bodyStr)
|
||||
|
||||
service, found := h.findServiceByName(serviceName)
|
||||
if !found {
|
||||
controllers.RespondWithError(w, http.StatusNotFound, fmt.Sprintf("Servicio no encontrado: %s", serviceName))
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
respChannel := make(chan interface{}, 1)
|
||||
go func() {
|
||||
resp := h.executeService(ctx, service, r, serviceName)
|
||||
respChannel <- resp
|
||||
}()
|
||||
resp := <-respChannel
|
||||
//controllers.RespondWithSuccess(w, http.StatusAccepted, fmt.Sprintf("Service %s execution initiated", serviceName), resp)
|
||||
controllers.RespondWithSuccess(w, 200, fmt.Sprintf("Listado del servicio %s", serviceName), resp)
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) readRequestBody(r *http.Request) (string, error) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
r.Body.Close()
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) findServiceByName(name string) (models.SoapService, bool) {
|
||||
for _, svc := range h.services {
|
||||
if svc.GetName() == name {
|
||||
return svc, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) executeService(ctx context.Context, service models.SoapService, r *http.Request, serviceName string) interface{} {
|
||||
resp, err := service.Execute(ctx, r, h.isCron)
|
||||
if err != nil {
|
||||
log.Printf("Error al ejecutar el servicio %s: %v", serviceName, err)
|
||||
return nil
|
||||
} else {
|
||||
log.Printf("Servicio %s ejecutado satisfactoriamente", serviceName)
|
||||
return resp
|
||||
}
|
||||
}
|
||||
|
||||
// registrar empresa -------------------------------------------------------------------------------------------------------
|
||||
func (h *ServiceHandler) RunGetCodeService(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
serviceName := vars["obtenerCodigo"]
|
||||
|
||||
service, found := h.findGetCodeByName(serviceName)
|
||||
if !found {
|
||||
controllers.RespondWithError(w, http.StatusNotFound, fmt.Sprintf("Servicio no encontrado: %s", serviceName))
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
respChannel := make(chan interface{}, 1)
|
||||
go func() {
|
||||
respCuis := h.executeGetCodeService(ctx, service, w, r, serviceName)
|
||||
respChannel <- respCuis
|
||||
|
||||
}()
|
||||
respCuis := <-respChannel
|
||||
controllers.RespondCuisWithSuccess(w, http.StatusAccepted, fmt.Sprintf("Servicio %s ejecucion iniciada", respCuis), respCuis)
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) executeGetCodeService(ctx context.Context, service obtencionCodigos.CuisService, w http.ResponseWriter, r *http.Request, serviceName string) interface{} {
|
||||
//var result interface{}
|
||||
respCuis, err := service.ExecuteGetCode(ctx, w, r, h.isCron)
|
||||
if err != nil {
|
||||
log.Printf("Error ejecutando servicio %s: %v", serviceName, err)
|
||||
return err // o podrías retornar el error
|
||||
} else {
|
||||
log.Printf("Servicio %s ejecutado satisfactoriamente", serviceName)
|
||||
return respCuis // Aquí retornarías el resultado del servicio
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) findGetCodeByName(name string) (obtencionCodigos.CuisService, bool) {
|
||||
for _, svc := range h.serviceCuis {
|
||||
if svc.GetName() == name {
|
||||
return svc, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
12
daemonService/internal/models/errorGenerico.go
Normal file
12
daemonService/internal/models/errorGenerico.go
Normal file
@ -0,0 +1,12 @@
|
||||
package models
|
||||
|
||||
type ApiErrorCustom struct {
|
||||
Success bool `json:"success"`
|
||||
Status int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (e ApiErrorCustom) Error() string {
|
||||
return e.Message
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package obtencionCodigos
|
||||
|
||||
//type CuisRequestModel struct {
|
||||
// CodigoAmbiente string `json:"CodigoAmbiente"`
|
||||
// CodigoModalidad string `json:"codigoModalidad"`
|
||||
// CodigoPuntoVenta string `json:"CodigoPuntoVenta"`
|
||||
// CodigoSistema string `json:"CodigoSistema"`
|
||||
// CodigoSucursal string `json:"CodigoSucursal"`
|
||||
// Nit string `json:"Nit"`
|
||||
//}
|
@ -0,0 +1,34 @@
|
||||
package obtencionCodigos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/notifications"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// interface
|
||||
type CuisService interface {
|
||||
ExecuteGetCode(ctx context.Context, w http.ResponseWriter, req *http.Request, isCron bool) (interface{}, error)
|
||||
GetName() string
|
||||
}
|
||||
|
||||
// configuración y dependencias para cada servicio
|
||||
type CuisServiceModel struct {
|
||||
Name string
|
||||
Enabled bool
|
||||
Concurrency int
|
||||
DB *sql.DB
|
||||
SOAPEndpoint string
|
||||
SOAPTimeout time.Duration
|
||||
SOAPRetries int
|
||||
NotificationSender notifications.NotificationSender
|
||||
//APIKey string
|
||||
TokenKey string
|
||||
TokenValue string
|
||||
TagNames string
|
||||
TagNamesCufd string
|
||||
QueryInsert string
|
||||
MsgCustomResponse string
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package obtencionCodigos
|
||||
|
||||
import "time"
|
||||
|
||||
// mapea toda la tabla registroEmpresa
|
||||
type RegistroEmpresa struct {
|
||||
ID int `sql:"id"`
|
||||
CodigoAmbiente int `sql:"codigo_ambiente"`
|
||||
CodigoModalidad int `sql:"codigo_modalidad"`
|
||||
CodigoPuntoVenta int `sql:"codigo_punto_venta"`
|
||||
CodigoSistema string `sql:"codigo_sistema"`
|
||||
CodigoSucursal int `sql:"codigo_sucursal"`
|
||||
Nit string `sql:"nit"`
|
||||
FechaCreacion time.Time `sql:"fecha_creacion"`
|
||||
FechaActualizacion time.Time `sql:"fecha_actualizacion"`
|
||||
TokenKey string `sql:"token_key"`
|
||||
TokenValue string `sql:"token_value"`
|
||||
NombreArchivoCertificado string `sql:"nombre_archivo_certificado"`
|
||||
NombreArchivoClavePrivada string `sql:"nombre_archivo_clave_privada"`
|
||||
}
|
||||
|
||||
// solo tiene los dos campos que quieres de la tabla cuis
|
||||
type CuisMinimal struct {
|
||||
Cuis_id int64 `sql:"id"`
|
||||
Cuis string `sql:"cuis"`
|
||||
FechaVigencia time.Time `sql:"fecha_vigencia"`
|
||||
}
|
||||
|
||||
// agrupa una empresa con sus cuis mínimos
|
||||
type EmpresaConCuis struct {
|
||||
RegistroEmpresa
|
||||
Cuis []CuisMinimal
|
||||
}
|
||||
|
||||
//import (
|
||||
//"time"
|
||||
//)
|
||||
//
|
||||
//// RegistroEmpresa mapea toda la tabla registroEmpresa
|
||||
//type RegistroEmpresa struct {
|
||||
// ID int `sql:"id"`
|
||||
// CodigoAmbiente int `sql:"codigo_ambiente"`
|
||||
// CodigoModalidad int `sql:"codigo_modalidad"`
|
||||
// CodigoPuntoVenta int `sql:"codigo_punto_venta"`
|
||||
// CodigoSistema string `sql:"codigo_sistema"`
|
||||
// CodigoSucursal int `sql:"codigo_sucursal"`
|
||||
// Nit string `sql:"nit"`
|
||||
// FechaCreacion time.Time `sql:"fecha_creacion"`
|
||||
// FechaActualizacion time.Time `sql:"fecha_actualizacion"`
|
||||
//}
|
||||
//
|
||||
//// CuisMinimal solo tiene los dos campos que quieres de la tabla cuis
|
||||
//type CuisMinimal struct {
|
||||
// Cuis_id int64 `sql:"id"`
|
||||
// Cuis string `sql:"cuis"`
|
||||
// FechaVigencia time.Time `sql:"fecha_vigencia"`
|
||||
//}
|
||||
//
|
||||
//// EmpresaConCuis agrupa una empresa con sus cuis mínimos
|
||||
//type EmpresaConCuis struct {
|
||||
// RegistroEmpresa
|
||||
// Cuis []CuisMinimal
|
||||
//}
|
@ -0,0 +1,32 @@
|
||||
package request
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// SOAPRequestEnvelope define la estructura general del mensaje SOAP.
|
||||
type SOAPRequestEnvelopeCufd struct {
|
||||
XMLName xml.Name `xml:"soapenv:Envelope"`
|
||||
XmlnsSoapenv string `xml:"xmlns:soapenv,attr"` // URL del namespace SOAP
|
||||
XmlnsSiat string `xml:"xmlns:siat,attr"` // URL del namespace siat
|
||||
Header *SOAPRequestHeaderCufd `xml:"soapenv:Header,omitempty"`
|
||||
Body SOAPRequestBodyCufd `xml:"soapenv:Body"`
|
||||
}
|
||||
|
||||
type SOAPRequestHeaderCufd struct{}
|
||||
|
||||
type SOAPRequestBodyCufd struct {
|
||||
Operacion OperacionXMLCufd `xml:",any,omitempty"`
|
||||
}
|
||||
|
||||
type OperacionXMLCufd struct {
|
||||
XMLName xml.Name
|
||||
Solicitud SolicitudCuisCufd `xml:"SolicitudCufd"`
|
||||
}
|
||||
|
||||
// permite controlar la serialización de OperacionXML para usar el nombre de etiqueta deseado.
|
||||
func (o OperacionXMLCufd) MarshalXML2(e *xml.Encoder, start xml.StartElement) error {
|
||||
start.Name = o.XMLName
|
||||
// Encapsulamos la solicitud en una estructura anónima para que se serialice correctamente.
|
||||
return e.EncodeElement(struct {
|
||||
Solicitud SolicitudCuisCufd `xml:"SolicitudCufd"`
|
||||
}{o.Solicitud}, start)
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package request
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// SOAPRequestEnvelope define la estructura general del mensaje SOAP.
|
||||
type SOAPRequestEnvelope struct {
|
||||
XMLName xml.Name `xml:"soapenv:Envelope"`
|
||||
XmlnsSoapenv string `xml:"xmlns:soapenv,attr"` // URL del namespace SOAP
|
||||
XmlnsSiat string `xml:"xmlns:siat,attr"` // URL del namespace siat
|
||||
Header *SOAPRequestHeader `xml:"soapenv:Header,omitempty"`
|
||||
Body SOAPRequestBody `xml:"soapenv:Body"`
|
||||
}
|
||||
|
||||
type SOAPRequestHeader struct{}
|
||||
|
||||
type SOAPRequestBody struct {
|
||||
Operacion OperacionXML `xml:",any,omitempty"`
|
||||
}
|
||||
|
||||
// /////////////////////////////// CUIS //////////////////////////////////////
|
||||
type OperacionXML struct {
|
||||
XMLName xml.Name
|
||||
Solicitud SolicitudCuisCufd `xml:"SolicitudCuis"`
|
||||
}
|
||||
|
||||
// permite controlar la serialización de OperacionXML para usar el nombre de etiqueta deseado.
|
||||
func (o OperacionXML) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
start.Name = o.XMLName
|
||||
// Encapsulamos la solicitud en una estructura anónima para que se serialice correctamente.
|
||||
return e.EncodeElement(struct {
|
||||
Solicitud SolicitudCuisCufd `xml:"SolicitudCuis"`
|
||||
}{o.Solicitud}, start)
|
||||
}
|
||||
|
||||
// /////////////////////////////// CUIS //////////////////////////////////////
|
||||
|
||||
type SolicitudCuisCufd struct {
|
||||
CodigoAmbiente string `xml:"codigoAmbiente,omitempty" json:"codigoAmbiente"`
|
||||
CodigoModalidad string `xml:"codigoModalidad,omitempty" json:"codigoModalidad"`
|
||||
CodigoPuntoVenta string `xml:"codigoPuntoVenta,omitempty" json:"codigoPuntoVenta"`
|
||||
CodigoSistema string `xml:"codigoSistema,omitempty" json:"codigoSistema"`
|
||||
CodigoSucursal string `xml:"codigoSucursal,omitempty" json:"codigoSucursal"`
|
||||
Nit string `xml:"nit,omitempty" json:"nit"`
|
||||
Cuis string `xml:"cuis,omitempty" json:"cuis,omitempty"`
|
||||
KeyToken string `xml:"keyToken,omitempty" json:"keyToken,omitempty"`
|
||||
ValueToken string `xml:"valueToken,omitempty" json:"valueToken,omitempty"`
|
||||
NombreArchivoCertificado string `xml:"nombreArchivoCertificado,omitempty" json:"nombreArchivoCertificado,omitempty"`
|
||||
NombreArchivoClavePrivada string `xml:"nombreArchivoClavePrivada,omitempty" json:"nombreArchivoClavePrivada,omitempty"`
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package response
|
||||
|
||||
// Fault estándar
|
||||
type SoapFault struct {
|
||||
FaultCode string `xml:"faultcode"`
|
||||
FaultString string `xml:"faultstring"`
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package response
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// Estructuras para respuesta SOAP
|
||||
type SoapEnvelopeCufd struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyCufd `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyCufd struct {
|
||||
Response *CufdResponse `xml:"cufdResponse,omitempty" json:"response,omitempty"`
|
||||
ResponseError *SoapFault `xml:"Fault,omitempty" json:"responseError,omitempty"`
|
||||
}
|
||||
|
||||
type CufdResponse struct {
|
||||
Respuesta RespuestaCufd `xml:"RespuestaCufd"`
|
||||
}
|
||||
|
||||
type RespuestaCufd struct {
|
||||
Codigo string `xml:"codigo,omitempty" json:"codigo,omitempty"`
|
||||
CodigoControl string `xml:"codigoControl,omitempty" json:"codigoControl,omitempty"`
|
||||
Direccion string `xml:"direccion,omitempty" json:"direccion,omitempty"`
|
||||
FechaVigencia string `xml:"fechaVigencia,omitempty" json:"fechaVigencia,omitempty"`
|
||||
Transaccion bool `xml:"transaccion,omitempty" json:"transaccion,omitempty"`
|
||||
MensajesList []MsgDetalleCufd `xml:"mensajesList,omitempty" json:"mensajesList,omitempty"`
|
||||
}
|
||||
|
||||
// CUSTOMER RESPONSE: parte del Body se envia al CustomerResponseModel.go
|
||||
type MsgDetalleCufd struct {
|
||||
Codigo string `xml:"codigo,omitempty" json:"codigo,omitempty"`
|
||||
Descripcion string `xml:"descripcion,omitempty" json:"descripcion,omitempty"`
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package response
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// Estructuras para respuesta SOAP
|
||||
type SoapEnvelopeCuis struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyCuis `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyCuis struct {
|
||||
Response *CuisResponse `xml:"cuisResponse,omitempty" json:"response,omitempty"`
|
||||
ResponseError *SoapFault `xml:"Fault,omitempty" json:"responseError,omitempty"`
|
||||
}
|
||||
|
||||
type CuisResponse struct {
|
||||
Respuesta RespuestaCuis `xml:"RespuestaCuis"`
|
||||
}
|
||||
|
||||
type RespuestaCuis struct {
|
||||
Codigo string `xml:"codigo,omitempty" json:"codigo,omitempty"`
|
||||
FechaVigencia string `xml:"fechaVigencia,omitempty" json:"fechaVigencia,omitempty"`
|
||||
Transaccion bool `xml:"transaccion,omitempty" json:"transaccion,omitempty"`
|
||||
MensajesList []MsgDetalle `xml:"mensajesList,omitempty" json:"mensajesList,omitempty"`
|
||||
}
|
||||
|
||||
// CUSTOMER RESPONSE: parte del Body se envia al CustomerResponseModel.go
|
||||
type MsgDetalle struct {
|
||||
Codigo string `xml:"codigo,omitempty" json:"codigo,omitempty"`
|
||||
Mescripcion string `xml:"descripcion,omitempty" json:"descripcion,omitempty"`
|
||||
}
|
21
daemonService/internal/models/responseControllerModel.go
Normal file
21
daemonService/internal/models/responseControllerModel.go
Normal file
@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
type APIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type APIResponseCuis struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data CuisMinimal `json:"data,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type CuisMinimal struct {
|
||||
Codigo string `json:"codigo"`
|
||||
FechaVigencia string `json:"fechaVigencia"`
|
||||
Transaccion bool `json:"transaccion"`
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package bodyGeneric
|
||||
|
||||
type CustomResponse[T any] struct {
|
||||
Success bool `json:"success"`
|
||||
Status int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data T `json:"data"`
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TipoMoneda struct {
|
||||
ID int
|
||||
Codigo string
|
||||
ReqSoap sql.NullString
|
||||
RespSoap sql.NullString
|
||||
ReqJson sql.NullString
|
||||
RespJson sql.NullString
|
||||
FechaCreacion time.Time
|
||||
FechaActualizacion time.Time
|
||||
CUISID int
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package request
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// SOAPRequestEnvelope define la estructura general del mensaje SOAP.
|
||||
type SOAPRequestEnvelope struct {
|
||||
XMLName xml.Name `xml:"soapenv:Envelope"`
|
||||
XmlnsSoapenv string `xml:"xmlns:soapenv,attr"` // URL del namespace SOAP
|
||||
XmlnsSiat string `xml:"xmlns:siat,attr"` // URL del namespace siat
|
||||
Header *SOAPRequestHeader `xml:"soapenv:Header,omitempty"`
|
||||
//Body SOAPRequestBody `xml:"soapenv:Body"`
|
||||
Body SOAPRequestBody `xml:"soapenv:Body"`
|
||||
}
|
||||
|
||||
type SOAPRequestHeader struct{}
|
||||
|
||||
type SOAPRequestBody struct {
|
||||
Operacion OperacionXML `xml:",any"`
|
||||
}
|
||||
|
||||
type OperacionXML struct {
|
||||
XMLName xml.Name
|
||||
Solicitud SolicitudSincronizacion `xml:"SolicitudSincronizacion"`
|
||||
}
|
||||
|
||||
// MarshalXML permite controlar la serialización de OperacionXML para usar el nombre de etiqueta deseado.
|
||||
func (o OperacionXML) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
start.Name = o.XMLName
|
||||
// Encapsulamos la solicitud en una estructura anónima para que se serialice correctamente.
|
||||
return e.EncodeElement(struct {
|
||||
Solicitud SolicitudSincronizacion `xml:"SolicitudSincronizacion"`
|
||||
}{o.Solicitud}, start)
|
||||
}
|
||||
|
||||
// SolicitudSincronizacion representa la información de la sincronización que se debe enviar.
|
||||
type SolicitudSincronizacion struct {
|
||||
CodigoAmbiente string `xml:"codigoAmbiente,omitempty"`
|
||||
CodigoPuntoVenta string `xml:"codigoPuntoVenta,omitempty"`
|
||||
CodigoSistema string `xml:"codigoSistema,omitempty"`
|
||||
CodigoSucursal string `xml:"codigoSucursal,omitempty"`
|
||||
Cuis string `xml:"cuis,omitempty"`
|
||||
Nit string `xml:"nit,omitempty"`
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Estructuras para respuesta SOAP
|
||||
type SoapEnvelopeLeyendas struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyLeyendas `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyLeyendas struct {
|
||||
Response SincronizarLeyendasResponse `xml:"sincronizarListaLeyendasFacturaResponse"`
|
||||
}
|
||||
|
||||
type SincronizarLeyendasResponse struct {
|
||||
Respuesta RespuestaListaLeyendas `xml:"RespuestaListaParametricasLeyendas"`
|
||||
}
|
||||
|
||||
type RespuestaListaLeyendas struct {
|
||||
Transaccion bool `xml:"transaccion" json:"transaccion"`
|
||||
ListaLeyendas []LeyendaDetalle `xml:"listaLeyendas" json:"listaLeyendas"`
|
||||
}
|
||||
|
||||
// CUSTOMER RESPONSE: parte del Body se envia al CustomerResponseModel.go
|
||||
type LeyendaDetalle struct {
|
||||
CodigoProducto string `xml:"codigoProducto,omitempty" json:"codigo,omitempty"`
|
||||
CodigoActividad string `xml:"codigoActividad" json:"codigoActividad"`
|
||||
DescripcionLeyenda string `xml:"descripcionLeyenda" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeLeyendas) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.Respuesta.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeLeyendas) GetData() []LeyendaDetalle {
|
||||
return e.Body.Response.Respuesta.ListaLeyendas
|
||||
}
|
||||
|
||||
// Método modificado para usar SoapEnvelopeLeyendas como receptor
|
||||
func (r SoapEnvelopeLeyendas) AsignarCodigosSecuenciales() {
|
||||
for i := range r.Body.Response.Respuesta.ListaLeyendas {
|
||||
// Asignar un código secuencial comenzando desde 1
|
||||
//r.Body.Response.Respuesta.ListaLeyendas[i].CodigoProducto = fmt.Sprintf("PROD%04d", i+1)
|
||||
r.Body.Response.Respuesta.ListaLeyendas[i].CodigoProducto = fmt.Sprintf("%d", i+1)
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopeProductoServicio struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyProductoServicio `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyProductoServicio struct {
|
||||
Response SincronizarResponseProductoServicio `xml:"sincronizarListaProductosServiciosResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseProductoServicio struct {
|
||||
RespuestaLista RespuestaListaParametricasProductoServicio `xml:"RespuestaListaProductos"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasProductoServicio struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []ProductoServicio `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type ProductoServicio struct {
|
||||
CodigoActividad string `xml:"codigoActividad,omitempty" json:"codigoActividad,omitempty"`
|
||||
CodigoProducto string `xml:"codigoProducto,omitempty" json:"codigo,omitempty"`
|
||||
DescripcionProducto string `xml:"descripcionProducto,omitempty" json:"descripcion,omitempty"`
|
||||
Nandina []string `xml:"nandina,omitempty" json:"nandina,omitempty"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeProductoServicio) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeProductoServicio) GetData() []ProductoServicio {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// Estructuras para la respuesta SOAP.
|
||||
type SoapEnvelopeActividad struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyActividad `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyActividad struct {
|
||||
Response SincronizarResponseActividad `xml:"sincronizarActividadesResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseActividad struct {
|
||||
RespuestaLista RespuestaListaActividades `xml:"RespuestaListaActividades"`
|
||||
}
|
||||
|
||||
type RespuestaListaActividades struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []ListaActividades `xml:"listaActividades"`
|
||||
}
|
||||
|
||||
type ListaActividades struct {
|
||||
CodigoCaeb string `xml:"codigoCaeb,omitempty" json:"codigo,omitempty"`
|
||||
Descripcion string `xml:"descripcion,omitempty" json:"descripcion,omitempty"`
|
||||
TipoActividad string `xml:"tipoActividad,omitempty" json:"tipoActividad,omitempty"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeActividad) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeActividad) GetData() []ListaActividades {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopeEventoSignificativo struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyEventoSignificativo `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyEventoSignificativo struct {
|
||||
Response SincronizarResponseEventoSignificativo `xml:"sincronizarParametricaEventosSignificativosResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseEventoSignificativo struct {
|
||||
RespuestaLista RespuestaListaParametricasEventoSignificativo `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasEventoSignificativo struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []EventoSignificativo `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type EventoSignificativo struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeEventoSignificativo) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeEventoSignificativo) GetData() []EventoSignificativo {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// Estructuras para la respuesta SOAP.
|
||||
type SoapEnvelopeFechaHora struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyFechaHora `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyFechaHora struct {
|
||||
Response SincronizarResponseFechaHora `xml:"sincronizarFechaHoraResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseFechaHora struct {
|
||||
RespuestaFechaHora RespuestaFechaHora `xml:"RespuestaFechaHora"`
|
||||
}
|
||||
|
||||
type RespuestaFechaHora struct {
|
||||
//Transaccion bool `xml:"transaccion"`
|
||||
FechaHora string `xml:"fechaHora"`
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeFechaHora) GetData() RespuestaFechaHora {
|
||||
return e.Body.Response.RespuestaFechaHora
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopeMensajeServicio struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyMensajeServicio `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyMensajeServicio struct {
|
||||
Response SincronizarResponseMensajeServicio `xml:"sincronizarListaMensajesServiciosResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseMensajeServicio struct {
|
||||
RespuestaLista RespuestaListaParametricasMensajeServicio `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasMensajeServicio struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []MensajeServicio `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type MensajeServicio struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeMensajeServicio) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeMensajeServicio) GetData() []MensajeServicio {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopePaisOrigen struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyPaisOrigen `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyPaisOrigen struct {
|
||||
Response SincronizarResponsePaisOrigen `xml:"sincronizarParametricaPaisOrigenResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponsePaisOrigen struct {
|
||||
RespuestaLista RespuestaListaParametricasPaisOrigen `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasPaisOrigen struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []PaisOrigen `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type PaisOrigen struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopePaisOrigen) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopePaisOrigen) GetData() []PaisOrigen {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopeTipoHabitacion struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyTipoHabitacion `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyTipoHabitacion struct {
|
||||
Response SincronizarResponseTipoHabitacion `xml:"sincronizarParametricaTipoHabitacionResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseTipoHabitacion struct {
|
||||
RespuestaLista RespuestaListaParametricasTipoHabitacion `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasTipoHabitacion struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []TipoHabitacion `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type TipoHabitacion struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeTipoHabitacion) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeTipoHabitacion) GetData() []TipoHabitacion {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package response
|
||||
|
||||
// Interfaz para respuestas SOAP que tienen transacción y datos
|
||||
type SoapResponse[T any] interface {
|
||||
IsTransactionSuccessful() bool
|
||||
GetData() T
|
||||
}
|
||||
|
||||
type SoapResponseLeyenda[T any] interface {
|
||||
IsTransactionSuccessful() bool
|
||||
AsignarCodigosSecuenciales()
|
||||
GetData() T
|
||||
}
|
||||
|
||||
type SoapResponseFechaHora[T any] interface {
|
||||
GetData() T
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopeTipoActividadesSector struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyTipoActividadesSector `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyTipoActividadesSector struct {
|
||||
Response SincronizarResponseTipoActividadesSector `xml:"sincronizarListaActividadesDocumentoSectorResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseTipoActividadesSector struct {
|
||||
RespuestaLista RespuestaListaParametricasTipoActividadesSector `xml:"RespuestaListaActividadesDocumentoSector"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasTipoActividadesSector struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []TipoActividadesSector `xml:"listaActividadesDocumentoSector"`
|
||||
}
|
||||
|
||||
type TipoActividadesSector struct {
|
||||
CodigoActividad string `xml:"codigoActividad,omitempty" json:"codigo,omitempty"`
|
||||
CodigoDocumentoSector string `xml:"codigoDocumentoSector,omitempty" json:"codigoDocumentoSector,omitempty"`
|
||||
TipoDocumentoSector string `xml:"tipoDocumentoSector,omitempty" json:"descripcion,omitempty"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeTipoActividadesSector) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeTipoActividadesSector) GetData() []TipoActividadesSector {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopeTipoDocumento struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyTipoDocumento `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyTipoDocumento struct {
|
||||
Response SincronizarResponseTipoDocumento `xml:"sincronizarParametricaTipoDocumentoIdentidadResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseTipoDocumento struct {
|
||||
RespuestaLista RespuestaListaParametricasTipoDocumento `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasTipoDocumento struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []TipoDocumento `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type TipoDocumento struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeTipoDocumento) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeTipoDocumento) GetData() []TipoDocumento {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopeTipoSector struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyTipoSector `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyTipoSector struct {
|
||||
Response SincronizarResponseTipoSector `xml:"sincronizarParametricaTipoDocumentoSectorResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseTipoSector struct {
|
||||
RespuestaLista RespuestaListaParametricasTipoSector `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasTipoSector struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []TipoSector `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type TipoSector struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeTipoSector) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeTipoSector) GetData() []TipoSector {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// Estructuras para la respuesta SOAP.
|
||||
type SoapEnvelopeTipoEmision struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyTipoEmision `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyTipoEmision struct {
|
||||
Response SincronizarResponseTipoEmision `xml:"sincronizarParametricaTipoEmisionResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseTipoEmision struct {
|
||||
RespuestaLista RespuestaListaParametricasTipoEmision `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasTipoEmision struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []TipoEmision `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type TipoEmision struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeTipoEmision) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeTipoEmision) GetData() []TipoEmision {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// Estructuras para la respuesta SOAP.
|
||||
type SoapEnvelopeTipoFactura struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyTipoFactura `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyTipoFactura struct {
|
||||
Response SincronizarResponseTipoFactura `xml:"sincronizarParametricaTiposFacturaResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseTipoFactura struct {
|
||||
RespuestaLista RespuestaListaParametricasTipoFactura `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasTipoFactura struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []TipoFactura `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type TipoFactura struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeTipoFactura) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeTipoFactura) GetData() []TipoFactura {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// Estructuras para la respuesta SOAP.
|
||||
type SoapEnvelopeMetodoPago struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyMetodoPago `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyMetodoPago struct {
|
||||
Response SincronizarResponseMetodoPago `xml:"sincronizarParametricaTipoMetodoPagoResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseMetodoPago struct {
|
||||
RespuestaLista RespuestaListaParametricasMetodoPago `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasMetodoPago struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []MetodoPago `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type MetodoPago struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeMetodoPago) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeMetodoPago) GetData() []MetodoPago {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// Estructuras para la respuesta SOAP.
|
||||
type SoapEnvelopeMoneda struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyMoneda `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyMoneda struct {
|
||||
Response SincronizarResponseMoneda `xml:"sincronizarParametricaTipoMonedaResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseMoneda struct {
|
||||
RespuestaLista RespuestaListaParametricasMoneda `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasMoneda struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []Moneda `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type Moneda struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeMoneda) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeMoneda) GetData() []Moneda {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopeMotivoAnulacion struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyMotivoAnulacion `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyMotivoAnulacion struct {
|
||||
Response SincronizarResponseMotivoAnulacion `xml:"sincronizarParametricaMotivoAnulacionResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseMotivoAnulacion struct {
|
||||
RespuestaLista RespuestaListaParametricasMotivoAnulacion `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasMotivoAnulacion struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []MotivoAnulacion `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type MotivoAnulacion struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeMotivoAnulacion) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeMotivoAnulacion) GetData() []MotivoAnulacion {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// Estructuras para la respuesta SOAP.
|
||||
type SoapEnvelopePuntoVenta struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyPuntoVenta `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyPuntoVenta struct {
|
||||
Response SincronizarResponsePuntoVenta `xml:"sincronizarParametricaTipoPuntoVentaResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponsePuntoVenta struct {
|
||||
RespuestaLista RespuestaListaParametricasPuntoVenta `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasPuntoVenta struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []PuntoVenta `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type PuntoVenta struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopePuntoVenta) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopePuntoVenta) GetData() []PuntoVenta {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type SoapEnvelopeUnidadMedida struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body SoapBodyUnidadMedida `xml:"Body"`
|
||||
}
|
||||
|
||||
type SoapBodyUnidadMedida struct {
|
||||
Response SincronizarResponseUnidadMedida `xml:"sincronizarParametricaUnidadMedidaResponse"`
|
||||
}
|
||||
|
||||
type SincronizarResponseUnidadMedida struct {
|
||||
RespuestaLista RespuestaListaParametricasUnidadMedida `xml:"RespuestaListaParametricas"`
|
||||
}
|
||||
|
||||
type RespuestaListaParametricasUnidadMedida struct {
|
||||
Transaccion bool `xml:"transaccion"`
|
||||
ListaCodigos []UnidadMedida `xml:"listaCodigos"`
|
||||
}
|
||||
|
||||
type UnidadMedida struct {
|
||||
CodigoClasificador string `xml:"codigoClasificador" json:"codigo"`
|
||||
Descripcion string `xml:"descripcion" json:"descripcion"`
|
||||
}
|
||||
|
||||
// Implementaciones para cada tipo de envelope
|
||||
func (e SoapEnvelopeUnidadMedida) IsTransactionSuccessful() bool {
|
||||
return e.Body.Response.RespuestaLista.Transaccion
|
||||
}
|
||||
|
||||
func (e SoapEnvelopeUnidadMedida) GetData() []UnidadMedida {
|
||||
return e.Body.Response.RespuestaLista.ListaCodigos
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/notifications"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Interfaz base que todos implementan
|
||||
type SoapService interface {
|
||||
Execute(ctx context.Context, req *http.Request, isCron bool) (interface{}, error)
|
||||
GetName() string
|
||||
}
|
||||
|
||||
// configuración y dependencias para cada servicio
|
||||
type ServiceModel struct {
|
||||
Name string
|
||||
Enabled bool
|
||||
Concurrency int
|
||||
DB *sql.DB
|
||||
SOAPEndpoint string
|
||||
SOAPTimeout time.Duration
|
||||
SOAPRetries int
|
||||
NotificationSender notifications.NotificationSender
|
||||
//APIKey string
|
||||
KeyToken string
|
||||
ValueToken string
|
||||
TagNames string
|
||||
QueryInsert string
|
||||
MsgCustomResponse string
|
||||
}
|
271
daemonService/internal/notifications/notifier.go
Normal file
271
daemonService/internal/notifications/notifier.go
Normal file
@ -0,0 +1,271 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/smtp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NotificationSender es la interfaz que deben cumplir los sistemas de notificación.
|
||||
type NotificationSender interface {
|
||||
SendNotification(subject, message string) error
|
||||
}
|
||||
|
||||
// WebhookNotifier implementa la notificación vía webhook.
|
||||
type WebhookNotifier struct {
|
||||
WebhookURL string
|
||||
}
|
||||
|
||||
// SendNotification envía notificación a través de webhook.
|
||||
func (w *WebhookNotifier) SendNotification(subject, message string) error {
|
||||
if w.WebhookURL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(map[string]string{
|
||||
"subject": subject,
|
||||
"message": message,
|
||||
"time": time.Now().Format(time.RFC3339),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.Post(w.WebhookURL, "application/json", bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("webhook error: %s", resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmailNotifier simula el envío de notificaciones por correo electrónico.
|
||||
type EmailNotifier struct {
|
||||
SMTPServer string
|
||||
SMTPPort string
|
||||
Username string
|
||||
Password string
|
||||
FromEmail string
|
||||
ToEmail string
|
||||
}
|
||||
|
||||
// SendNotification envía un email a través de un servidor SMTP.
|
||||
func (e *EmailNotifier) SendNotification(subject, message string) error {
|
||||
// Construir los encabezados del mensaje
|
||||
header := make(map[string]string)
|
||||
header["From"] = e.FromEmail
|
||||
header["To"] = e.ToEmail
|
||||
header["Subject"] = subject
|
||||
header["MIME-Version"] = "1.0"
|
||||
header["Content-Type"] = "text/plain; charset=\"utf-8\""
|
||||
|
||||
var msg string
|
||||
for k, v := range header {
|
||||
msg += fmt.Sprintf("%s: %s\r\n", k, v)
|
||||
}
|
||||
msg += "\r\n" + message
|
||||
|
||||
// Configurar autenticación SMTP
|
||||
auth := smtp.PlainAuth("", e.Username, e.Password, e.SMTPServer)
|
||||
addr := fmt.Sprintf("%s:%s", e.SMTPServer, e.SMTPPort)
|
||||
|
||||
// Enviar el email
|
||||
err := smtp.SendMail(addr, auth, e.FromEmail, []string{e.ToEmail}, []byte(msg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Email enviado correctamente a %s", e.ToEmail)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SlackNotifier implementa las notificaciones vía Slack.
|
||||
type SlackNotifier struct {
|
||||
WebhookURL string
|
||||
}
|
||||
|
||||
// SendNotification envía un mensaje a Slack.
|
||||
func (s *SlackNotifier) SendNotification(subject, message string) error {
|
||||
if s.WebhookURL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(map[string]interface{}{
|
||||
"text": fmt.Sprintf("*%s*\n%s", subject, message),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.Post(s.WebhookURL, "application/json", bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("slack webhook error: %s", resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateNotificationSender crea el notificador según la configuración.
|
||||
func CreateNotificationSender(enabled bool, method string, cfg map[string]string) NotificationSender {
|
||||
if !enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch method {
|
||||
case "webhook":
|
||||
return &WebhookNotifier{
|
||||
WebhookURL: cfg["url"],
|
||||
}
|
||||
case "email":
|
||||
return &EmailNotifier{
|
||||
SMTPServer: cfg["smtp_server"],
|
||||
SMTPPort: cfg["smtp_port"],
|
||||
Username: cfg["username"],
|
||||
Password: cfg["password"],
|
||||
FromEmail: cfg["from_email"],
|
||||
ToEmail: cfg["to_email"],
|
||||
}
|
||||
case "slack":
|
||||
return &SlackNotifier{
|
||||
WebhookURL: cfg["webhook_url"],
|
||||
}
|
||||
default:
|
||||
log.Printf("Método de notificación desconocido: %s", method)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// package notifications
|
||||
|
||||
// import (
|
||||
// "bytes"
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "log"
|
||||
// "net/http"
|
||||
// "time"
|
||||
// )
|
||||
|
||||
// // NotificationSender define la interfaz para enviar notificaciones.
|
||||
// type NotificationSender interface {
|
||||
// SendNotification(subject, message string) error
|
||||
// }
|
||||
|
||||
// // WebhookNotifier envía notificaciones mediante webhook.
|
||||
// type WebhookNotifier struct {
|
||||
// WebhookURL string
|
||||
// }
|
||||
|
||||
// func (w *WebhookNotifier) SendNotification(subject, message string) error {
|
||||
// if w.WebhookURL == "" {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// payload, err := json.Marshal(map[string]string{
|
||||
// "subject": subject,
|
||||
// "message": message,
|
||||
// "time": time.Now().Format(time.RFC3339),
|
||||
// })
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// resp, err := http.Post(w.WebhookURL, "application/json", bytes.NewBuffer(payload))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
// if resp.StatusCode >= 400 {
|
||||
// return fmt.Errorf("webhook error: %s", resp.Status)
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // EmailNotifier simula el envío de notificaciones por email.
|
||||
// type EmailNotifier struct {
|
||||
// SMTPServer string
|
||||
// SMTPPort string
|
||||
// Username string
|
||||
// Password string
|
||||
// FromEmail string
|
||||
// ToEmail string
|
||||
// }
|
||||
|
||||
// func (e *EmailNotifier) SendNotification(subject, message string) error {
|
||||
// log.Printf("Simulando envío de email: Asunto: %s, Mensaje: %s", subject, message)
|
||||
// log.Printf("Destinatario: %s, Remitente: %s", e.ToEmail, e.FromEmail)
|
||||
// // Aquí se integraría el cliente SMTP real si fuera necesario
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // SlackNotifier envía notificaciones a Slack.
|
||||
// type SlackNotifier struct {
|
||||
// WebhookURL string
|
||||
// }
|
||||
|
||||
// func (s *SlackNotifier) SendNotification(subject, message string) error {
|
||||
// if s.WebhookURL == "" {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// payload, err := json.Marshal(map[string]interface{}{
|
||||
// "text": fmt.Sprintf("*%s*\n%s", subject, message),
|
||||
// })
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// resp, err := http.Post(s.WebhookURL, "application/json", bytes.NewBuffer(payload))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
// if resp.StatusCode >= 400 {
|
||||
// return fmt.Errorf("slack webhook error: %s", resp.Status)
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // NewNotificationSender crea la instancia del notificador según la configuración.
|
||||
// func NewNotificationSender(enabled bool, method string, cfg map[string]string) NotificationSender {
|
||||
// if !enabled {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// switch method {
|
||||
// case "webhook":
|
||||
// return &WebhookNotifier{WebhookURL: cfg["url"]}
|
||||
// case "email":
|
||||
// return &EmailNotifier{
|
||||
// SMTPServer: cfg["smtp_server"],
|
||||
// SMTPPort: cfg["smtp_port"],
|
||||
// Username: cfg["username"],
|
||||
// Password: cfg["password"],
|
||||
// FromEmail: cfg["from_email"],
|
||||
// ToEmail: cfg["to_email"],
|
||||
// }
|
||||
// case "slack":
|
||||
// return &SlackNotifier{WebhookURL: cfg["webhook_url"]}
|
||||
// default:
|
||||
// log.Printf("Método de notificación desconocido: %s", method)
|
||||
// return nil
|
||||
// }
|
||||
// }
|
292
daemonService/internal/repositories/cuis_repo.go
Normal file
292
daemonService/internal/repositories/cuis_repo.go
Normal file
@ -0,0 +1,292 @@
|
||||
// repository/cuis_repo.go
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/models"
|
||||
"daemonService/internal/models/obtencionCodigos/request"
|
||||
"daemonService/internal/models/obtencionCodigos/response"
|
||||
srv "daemonService/internal/services"
|
||||
"daemonService/internal/utils"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CuisRepository struct {
|
||||
CuisService *srv.CuisService
|
||||
ModelService models.ServiceModel
|
||||
}
|
||||
|
||||
// instanciamos cuisRepository
|
||||
func NewCuisRepository(model models.ServiceModel, cuisService *srv.CuisService) *CuisRepository {
|
||||
return &CuisRepository{ModelService: model, CuisService: cuisService}
|
||||
}
|
||||
|
||||
func (cr *CuisRepository) CrearNuevoRegistro(ctx context.Context, request request.SolicitudCuisCufd, codigoCuis string, fechaVigencia string, transaccion bool, cufdData response.SoapBodyCufd) error {
|
||||
// Obtener la hora actual de Bolivia
|
||||
currentBolivia, err := utils.GetCurrentBoliviaTime()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error al obtener hora de Bolivia: %w", err)
|
||||
}
|
||||
|
||||
// Iniciar transacción
|
||||
tx, err := cr.ModelService.DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
cr.CuisService.LogMessage("Error al iniciar transacción: %v", err)
|
||||
return utils.ErrorCustom(http.StatusBadRequest, "Error al iniciar transacción")
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// Consulta corregida para manejar actualizaciones de CUIS y CUFD
|
||||
query := `
|
||||
WITH empresa AS (
|
||||
-- Inserta o actualiza empresa usando la clave natural
|
||||
INSERT INTO registroEmpresa (
|
||||
codigo_ambiente,
|
||||
codigo_modalidad,
|
||||
codigo_punto_venta,
|
||||
codigo_sistema,
|
||||
codigo_sucursal,
|
||||
nit,
|
||||
token_key,
|
||||
token_value,
|
||||
nombre_archivo_certificado,
|
||||
nombre_archivo_clave_privada
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
ON CONFLICT (nit, codigo_sucursal, codigo_punto_venta)
|
||||
DO UPDATE SET
|
||||
codigo_ambiente = EXCLUDED.codigo_ambiente,
|
||||
codigo_modalidad = EXCLUDED.codigo_modalidad,
|
||||
codigo_sistema = EXCLUDED.codigo_sistema,
|
||||
token_key = EXCLUDED.token_key,
|
||||
token_value = EXCLUDED.token_value,
|
||||
nombre_archivo_certificado = EXCLUDED.nombre_archivo_certificado,
|
||||
nombre_archivo_clave_privada = EXCLUDED.nombre_archivo_clave_privada,
|
||||
fecha_actualizacion = CURRENT_TIMESTAMP
|
||||
RETURNING id
|
||||
),
|
||||
cuis_existente AS (
|
||||
-- Busca si existe un CUIS para la empresa
|
||||
SELECT id, cuis, registro_empresa_id
|
||||
FROM cuis
|
||||
WHERE registro_empresa_id = (SELECT id FROM empresa)
|
||||
LIMIT 1
|
||||
),
|
||||
cuis_actualizado AS (
|
||||
-- Si existe CUIS pero es diferente, actualízalo
|
||||
UPDATE cuis
|
||||
SET
|
||||
cuis = $11,
|
||||
fecha_vigencia = $12,
|
||||
transaccion = $13,
|
||||
fecha_actualizacion = CURRENT_TIMESTAMP
|
||||
FROM cuis_existente
|
||||
WHERE cuis.id = cuis_existente.id
|
||||
AND (cuis_existente.cuis <> $11 OR cuis_existente.cuis IS NULL)
|
||||
RETURNING cuis.id, cuis.registro_empresa_id
|
||||
),
|
||||
cuis_insertado AS (
|
||||
-- Si no existe CUIS, inserta uno nuevo
|
||||
INSERT INTO cuis (
|
||||
cuis,
|
||||
fecha_vigencia,
|
||||
transaccion,
|
||||
fecha_creacion,
|
||||
registro_empresa_id
|
||||
)
|
||||
SELECT
|
||||
$11, $12, $13, $14, id
|
||||
FROM empresa
|
||||
WHERE NOT EXISTS (SELECT 1 FROM cuis_existente)
|
||||
RETURNING id, registro_empresa_id
|
||||
),
|
||||
cuis_final AS (
|
||||
-- Usa el CUIS actualizado si existe
|
||||
SELECT id, registro_empresa_id FROM cuis_actualizado
|
||||
UNION ALL
|
||||
-- O el insertado si no había uno previo
|
||||
SELECT id, registro_empresa_id FROM cuis_insertado
|
||||
UNION ALL
|
||||
-- O el existente si no se actualizó (porque era igual)
|
||||
SELECT id, registro_empresa_id FROM cuis_existente
|
||||
WHERE NOT EXISTS (SELECT 1 FROM cuis_actualizado)
|
||||
AND EXISTS (SELECT 1 FROM cuis_existente)
|
||||
),
|
||||
cufd_existente AS (
|
||||
-- Busca si existe un CUFD para el CUIS actual
|
||||
SELECT c.id, c.codigo, c.codigo_control, c.direccion, c.cuis_id
|
||||
FROM cufd c
|
||||
JOIN cuis_final cf ON c.cuis_id = cf.id
|
||||
LIMIT 1
|
||||
),
|
||||
cufd_actualizado AS (
|
||||
-- Si existe CUFD pero algún valor es diferente, actualízalo
|
||||
UPDATE cufd
|
||||
SET
|
||||
codigo = $15,
|
||||
codigo_control = $16,
|
||||
direccion = $17,
|
||||
fecha_vigencia = $18,
|
||||
transaccion = $19,
|
||||
fecha_actualizacion = CURRENT_TIMESTAMP
|
||||
FROM cufd_existente, cuis_final
|
||||
WHERE cufd.id = cufd_existente.id
|
||||
AND cufd.cuis_id = cuis_final.id
|
||||
AND (
|
||||
cufd_existente.codigo <> $15 OR
|
||||
cufd_existente.codigo_control <> $16 OR
|
||||
cufd_existente.direccion <> $17 OR
|
||||
cufd_existente.codigo IS NULL
|
||||
)
|
||||
RETURNING (SELECT registro_empresa_id FROM cuis_final LIMIT 1) as registro_id
|
||||
),
|
||||
cufd_insertado AS (
|
||||
-- Si no existe CUFD, inserta uno nuevo
|
||||
INSERT INTO cufd (
|
||||
codigo,
|
||||
codigo_control,
|
||||
direccion,
|
||||
fecha_vigencia,
|
||||
transaccion,
|
||||
fecha_creacion,
|
||||
cuis_id
|
||||
)
|
||||
SELECT
|
||||
$15, $16, $17, $18, $19, $20, id
|
||||
FROM cuis_final
|
||||
WHERE NOT EXISTS (SELECT 1 FROM cufd_existente)
|
||||
RETURNING (SELECT registro_empresa_id FROM cuis_final LIMIT 1) as registro_id
|
||||
),
|
||||
resultado_final AS (
|
||||
-- Usa el resultado de cualquiera de las operaciones
|
||||
SELECT registro_id FROM cufd_actualizado
|
||||
UNION ALL
|
||||
SELECT registro_id FROM cufd_insertado
|
||||
UNION ALL
|
||||
-- O usa el ID de empresa si el CUFD ya existía y no cambió
|
||||
SELECT registro_empresa_id as registro_id FROM cuis_final
|
||||
WHERE NOT EXISTS (SELECT 1 FROM cufd_actualizado)
|
||||
AND NOT EXISTS (SELECT 1 FROM cufd_insertado)
|
||||
AND EXISTS (SELECT 1 FROM cufd_existente)
|
||||
LIMIT 1
|
||||
)
|
||||
SELECT registro_id FROM resultado_final LIMIT 1;
|
||||
`
|
||||
|
||||
var registroID int
|
||||
err = tx.QueryRowContext(
|
||||
ctx,
|
||||
query,
|
||||
// Parámetros para registroEmpresa
|
||||
request.CodigoAmbiente,
|
||||
request.CodigoModalidad,
|
||||
request.CodigoPuntoVenta,
|
||||
request.CodigoSistema,
|
||||
request.CodigoSucursal,
|
||||
request.Nit,
|
||||
request.KeyToken,
|
||||
request.ValueToken,
|
||||
request.NombreArchivoCertificado, // Nuevo parámetro
|
||||
request.NombreArchivoClavePrivada, // Nuevo parámetro
|
||||
// Parámetros para cuis
|
||||
codigoCuis,
|
||||
fechaVigencia,
|
||||
transaccion,
|
||||
currentBolivia,
|
||||
// Parámetros para cufd
|
||||
cufdData.Response.Respuesta.Codigo,
|
||||
cufdData.Response.Respuesta.CodigoControl,
|
||||
cufdData.Response.Respuesta.Direccion,
|
||||
cufdData.Response.Respuesta.FechaVigencia,
|
||||
cufdData.Response.Respuesta.Transaccion,
|
||||
currentBolivia,
|
||||
).Scan(®istroID)
|
||||
|
||||
if err != nil {
|
||||
cr.CuisService.LogMessage("Error al insertar/actualizar registros: %v", err)
|
||||
return utils.ErrorCustom(http.StatusInternalServerError, "Error al procesar los registros en la base de datos")
|
||||
}
|
||||
|
||||
// Confirmar la transacción
|
||||
if err = tx.Commit(); err != nil {
|
||||
cr.CuisService.LogMessage("Error al confirmar transacción: %v", err)
|
||||
return utils.ErrorCustom(http.StatusInternalServerError, "Error al confirmar transacción")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// actualiza un registro existente de empresa y CUIS
|
||||
func (cr *CuisRepository) ActualizarRegistro(ctx context.Context, empresaID int, cuisID int64, req request.SolicitudCuisCufd, respRaw interface{}) error {
|
||||
currentBolivia, err := utils.GetCurrentBoliviaTime()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. Iniciar tx
|
||||
tx, err := cr.ModelService.DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
cr.CuisService.LogMessage("Error iniciar transacción: %d", err)
|
||||
return fmt.Errorf("%w", utils.ErrorCustom(http.StatusBadRequest, "Error iniciar transacción"))
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// 2. Parsear respuesta
|
||||
nuevoCuis, fechaVigencia, _, err := utils.ParseSoapCuisResponse(respRaw)
|
||||
if err != nil {
|
||||
cr.CuisService.LogMessage("Error al parsear la respuesta: %d", err)
|
||||
return fmt.Errorf("%w", utils.ErrorCustom(http.StatusBadRequest, "Error al parsear la respuesta"))
|
||||
}
|
||||
|
||||
// 3. Actualizar empresa
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
UPDATE registroEmpresa
|
||||
SET codigo_ambiente=$1, codigo_modalidad=$2, codigo_punto_venta=$3,
|
||||
codigo_sistema=$4, codigo_sucursal=$5, nit=$6, fecha_actualizacion=$7
|
||||
WHERE id=$8
|
||||
`,
|
||||
req.CodigoAmbiente, req.CodigoModalidad, req.CodigoPuntoVenta,
|
||||
req.CodigoSistema, req.CodigoSucursal, req.Nit, currentBolivia, empresaID,
|
||||
)
|
||||
if err != nil {
|
||||
cr.CuisService.LogMessage("Error al actualizar registroEmpresa: %d", err)
|
||||
return fmt.Errorf("%w", utils.ErrorCustom(http.StatusBadRequest, "Error al actualizar la tabla registroEmpresa"))
|
||||
}
|
||||
|
||||
// 4. Comprobar si hay un CUIS existente
|
||||
var existingCuis string
|
||||
err = tx.QueryRowContext(ctx, `SELECT cuis FROM cuis WHERE id=$1 AND registro_empresa_id=$2`, cuisID, empresaID).Scan(&existingCuis)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
cr.CuisService.LogMessage("Error al consultar cuis existente: %d", err)
|
||||
return fmt.Errorf("%w", utils.ErrorCustom(http.StatusBadRequest, "Error al consultar cuis existente"))
|
||||
}
|
||||
|
||||
// 5. Insertar o actualizar
|
||||
if err == sql.ErrNoRows {
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO cuis (registro_empresa_id, cuis) VALUES ($1, $2)`, empresaID, nuevoCuis)
|
||||
if err != nil {
|
||||
cr.CuisService.LogMessage("Error al insertar nuevo cuis: %d", err)
|
||||
return fmt.Errorf("%w", utils.ErrorCustom(http.StatusBadRequest, "Error al insertar nuevo cuis"))
|
||||
}
|
||||
} else if existingCuis != nuevoCuis {
|
||||
_, err = tx.ExecContext(ctx, `UPDATE cuis SET cuis=$1, fecha_vigencia=$2, fecha_actualizacion=$3 WHERE id=$4`,
|
||||
nuevoCuis, fechaVigencia, currentBolivia, cuisID)
|
||||
if err != nil {
|
||||
cr.CuisService.LogMessage("Error al actualizar cuis existente: %d", err)
|
||||
return fmt.Errorf("%w", utils.ErrorCustom(http.StatusBadRequest, "Error al actualizar cuis existente"))
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Confirmar tx
|
||||
if err := tx.Commit(); err != nil {
|
||||
cr.CuisService.LogMessage("Error al confirmar transacción: %d", err)
|
||||
return fmt.Errorf("%w", utils.ErrorCustom(http.StatusBadRequest, "Error al confirmar transacción"))
|
||||
}
|
||||
return nil
|
||||
}
|
124
daemonService/internal/repositories/empresa_repo.go
Normal file
124
daemonService/internal/repositories/empresa_repo.go
Normal file
@ -0,0 +1,124 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EmpresaRepository struct {
|
||||
Model obtencionCodigos.CuisServiceModel
|
||||
}
|
||||
|
||||
// instanciamos empresaRepository
|
||||
func NewEmpresaRepository(
|
||||
model obtencionCodigos.CuisServiceModel,
|
||||
) *EmpresaRepository {
|
||||
return &EmpresaRepository{Model: model}
|
||||
}
|
||||
|
||||
// obtiene todas las empresas con sus CUIS
|
||||
func (s *EmpresaRepository) GetEmpresasConCuisMinimal(ctx context.Context) ([]obtencionCodigos.EmpresaConCuis, error) {
|
||||
const query = `
|
||||
SELECT
|
||||
-- columnas de registroEmpresa
|
||||
re.id, re.codigo_ambiente, re.codigo_modalidad, re.codigo_punto_venta,
|
||||
re.codigo_sistema, re.codigo_sucursal, re.nit,
|
||||
re.fecha_creacion, re.fecha_actualizacion, re.token_key, re.token_value,
|
||||
re.nombre_archivo_clave_privada, re.nombre_archivo_certificado,
|
||||
c.id, c.cuis, c.fecha_vigencia
|
||||
FROM registroEmpresa re
|
||||
LEFT JOIN cuis c ON c.registro_empresa_id = re.id
|
||||
ORDER BY re.id;
|
||||
`
|
||||
|
||||
rows, err := s.Model.DB.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("consulta empresas con cuis minimal: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// Mapa temporal para agrupar por empresa.ID
|
||||
empresasMap := make(map[int]*obtencionCodigos.EmpresaConCuis)
|
||||
|
||||
for rows.Next() {
|
||||
// Variables temporales de escaneo
|
||||
var (
|
||||
id int
|
||||
codigoAmbiente int
|
||||
codigoModalidad int
|
||||
codigoPuntoVenta int
|
||||
codigoSistema string
|
||||
codigoSucursal int
|
||||
nit string
|
||||
fechaCreacion time.Time
|
||||
fechaActualizacion time.Time
|
||||
tokenKey string
|
||||
tokenValue string
|
||||
nombreArchivoClavePrivada string
|
||||
nombreArchivoCertificado string
|
||||
|
||||
idCuis sql.NullInt64
|
||||
cuis sql.NullString
|
||||
fechaVigencia sql.NullTime
|
||||
)
|
||||
|
||||
// Leer la fila
|
||||
if err := rows.Scan(
|
||||
&id, &codigoAmbiente, &codigoModalidad, &codigoPuntoVenta,
|
||||
&codigoSistema, &codigoSucursal, &nit,
|
||||
&fechaCreacion, &fechaActualizacion, &tokenKey, &tokenValue,
|
||||
&nombreArchivoClavePrivada, &nombreArchivoCertificado,
|
||||
&idCuis, &cuis, &fechaVigencia,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("scan fila: %w", err)
|
||||
}
|
||||
|
||||
// ¿Ya existe la empresa en el mapa?
|
||||
ent, ok := empresasMap[id]
|
||||
if !ok {
|
||||
// Si no existe, la creamos y rellenamos datos de registroEmpresa
|
||||
ent = &obtencionCodigos.EmpresaConCuis{
|
||||
RegistroEmpresa: obtencionCodigos.RegistroEmpresa{
|
||||
ID: id,
|
||||
CodigoAmbiente: codigoAmbiente,
|
||||
CodigoModalidad: codigoModalidad,
|
||||
CodigoPuntoVenta: codigoPuntoVenta,
|
||||
CodigoSistema: codigoSistema,
|
||||
CodigoSucursal: codigoSucursal,
|
||||
Nit: nit,
|
||||
FechaCreacion: fechaCreacion,
|
||||
FechaActualizacion: fechaActualizacion,
|
||||
TokenKey: tokenKey,
|
||||
TokenValue: tokenValue,
|
||||
NombreArchivoClavePrivada: nombreArchivoClavePrivada,
|
||||
NombreArchivoCertificado: nombreArchivoCertificado,
|
||||
},
|
||||
Cuis: make([]obtencionCodigos.CuisMinimal, 0, 1),
|
||||
}
|
||||
empresasMap[id] = ent
|
||||
}
|
||||
|
||||
// Si hay un CUIS válido, lo añadimos al slice
|
||||
if idCuis.Valid && cuis.Valid && fechaVigencia.Valid {
|
||||
ent.Cuis = append(ent.Cuis, obtencionCodigos.CuisMinimal{
|
||||
Cuis_id: idCuis.Int64,
|
||||
Cuis: cuis.String,
|
||||
FechaVigencia: fechaVigencia.Time,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iteración filas: %w", err)
|
||||
}
|
||||
|
||||
// Convertimos el mapa a slice ordenado por aparición
|
||||
resp := make([]obtencionCodigos.EmpresaConCuis, 0, len(empresasMap))
|
||||
for _, ent := range empresasMap {
|
||||
resp = append(resp, *ent)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/models"
|
||||
bg "daemonService/internal/models/sincronizacionDatos/bodyGeneric"
|
||||
"daemonService/internal/models/sincronizacionDatos/request"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// func (s *ListaLeyendasFacturaService) guardarRegistro(solicitud models.SolicitudSincronizacionLeyendas, cuisID int, soapReq string, bodyBytes []byte, leyendas []models.LeyendaDetalle) error {
|
||||
func GuardarRegistro[T any](s *models.ServiceModel, solicitud request.SolicitudSincronizacion, cuisID int, soapReq string, bodyBytes []byte, bodyGeneric T) error {
|
||||
dbCtx, dbCancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||||
defer dbCancel()
|
||||
|
||||
tx, err := s.DB.BeginTx(dbCtx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error iniciando transacción: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
solicitudJSON, _ := json.Marshal(solicitud)
|
||||
|
||||
// Creación de la respuesta utilizando la estructura genérica
|
||||
respJson := bg.CustomResponse[T]{
|
||||
Success: true,
|
||||
Status: 200,
|
||||
Message: s.MsgCustomResponse,
|
||||
Data: bodyGeneric,
|
||||
}
|
||||
|
||||
respuestaJSON, _ := json.Marshal(respJson)
|
||||
|
||||
_, err = tx.ExecContext(dbCtx, s.QueryInsert,
|
||||
solicitud.CodigoSistema,
|
||||
soapReq,
|
||||
string(bodyBytes),
|
||||
string(solicitudJSON),
|
||||
string(respuestaJSON),
|
||||
cuisID,
|
||||
)
|
||||
|
||||
//_, err = tx.ExecContext(dbCtx, `
|
||||
// INSERT INTO leyendas_factura (
|
||||
// codigo,
|
||||
// reqSoap,
|
||||
// respSoap,
|
||||
// reqJson,
|
||||
// respJson,
|
||||
// cuis_id,
|
||||
// fecha_creacion
|
||||
// )
|
||||
// VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz')
|
||||
// ON CONFLICT (codigo, cuis_id)
|
||||
// DO UPDATE SET
|
||||
// reqSoap = EXCLUDED.reqSoap,
|
||||
// respSoap = EXCLUDED.respSoap,
|
||||
// reqJson = EXCLUDED.reqJson,
|
||||
// respJson = EXCLUDED.respJson,
|
||||
// cuis_id = EXCLUDED.cuis_id,
|
||||
// fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz'`,
|
||||
// solicitud.CodigoSistema,
|
||||
// soapReq,
|
||||
// string(bodyBytes),
|
||||
// string(solicitudJSON),
|
||||
// string(respuestaJSON),
|
||||
// cuisID,
|
||||
//)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error ejecutando UPSERT: %v", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("error confirmando transacción: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
124
daemonService/internal/services/cuis_service.go
Normal file
124
daemonService/internal/services/cuis_service.go
Normal file
@ -0,0 +1,124 @@
|
||||
// service/cuis_service.go
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
"daemonService/internal/models/obtencionCodigos/request"
|
||||
"daemonService/internal/utils"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type CuisService struct {
|
||||
Model obtencionCodigos.CuisServiceModel
|
||||
Logger *log.Logger
|
||||
}
|
||||
|
||||
// En services/cuis_service.go
|
||||
func NewCuisService(
|
||||
model obtencionCodigos.CuisServiceModel, logger *log.Logger,
|
||||
) *CuisService {
|
||||
return &CuisService{Model: model, Logger: logger}
|
||||
}
|
||||
|
||||
// GetName retorna el nombre del servicio
|
||||
func (s *CuisService) GetName() string {
|
||||
return s.Model.Name
|
||||
}
|
||||
|
||||
// logMessage registra un mensaje con el prefijo del servicio
|
||||
func (s *CuisService) LogMessage(format string, v ...interface{}) {
|
||||
if s.Logger != nil {
|
||||
s.Logger.Printf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// registra un error y devuelve el mismo error
|
||||
func (s *CuisService) LogError(format string, err error, v ...interface{}) error {
|
||||
args := append([]interface{}{err}, v...)
|
||||
if s.Logger != nil {
|
||||
s.Logger.Printf(format, args...)
|
||||
}
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// crea una solicitud CUIS a partir de datos de empresa
|
||||
func (s *CuisService) BuildCuisRequestFromEmpresa(ec obtencionCodigos.EmpresaConCuis) request.SolicitudCuisCufd {
|
||||
return request.SolicitudCuisCufd{
|
||||
CodigoAmbiente: strconv.Itoa(ec.CodigoAmbiente),
|
||||
CodigoModalidad: strconv.Itoa(ec.CodigoModalidad),
|
||||
CodigoPuntoVenta: strconv.Itoa(ec.CodigoPuntoVenta),
|
||||
CodigoSistema: ec.CodigoSistema,
|
||||
CodigoSucursal: strconv.Itoa(ec.CodigoSucursal),
|
||||
Nit: ec.Nit,
|
||||
KeyToken: ec.TokenKey,
|
||||
ValueToken: ec.TokenValue,
|
||||
NombreArchivoClavePrivada: ec.NombreArchivoClavePrivada,
|
||||
NombreArchivoCertificado: ec.NombreArchivoCertificado,
|
||||
}
|
||||
}
|
||||
|
||||
// actualiza un registro existente de empresa y CUIS
|
||||
func (s *CuisService) actualizarRegistro(ctx context.Context, empresaID int, cuisID int64, req request.SolicitudCuisCufd, respRaw interface{}) error {
|
||||
currentBolivia, err := utils.GetCurrentBoliviaTime()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. Iniciar tx
|
||||
tx, err := s.Model.DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iniciar transacción: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// 2. Parsear respuesta
|
||||
nuevoCuis, fechaVigencia, _, err := utils.ParseSoapCuisResponse(respRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Actualizar empresa
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
UPDATE registroEmpresa
|
||||
SET codigo_ambiente=$1, codigo_modalidad=$2, codigo_punto_venta=$3,
|
||||
codigo_sistema=$4, codigo_sucursal=$5, nit=$6, fecha_actualizacion=$7
|
||||
WHERE id=$8
|
||||
`,
|
||||
req.CodigoAmbiente, req.CodigoModalidad, req.CodigoPuntoVenta,
|
||||
req.CodigoSistema, req.CodigoSucursal, req.Nit, currentBolivia, empresaID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("actualizar registroEmpresa: %w", err)
|
||||
}
|
||||
|
||||
// 4. Comprobar si hay un CUIS existente
|
||||
var existingCuis string
|
||||
err = tx.QueryRowContext(ctx, `SELECT cuis FROM cuis WHERE id=$1 AND registro_empresa_id=$2`, cuisID, empresaID).Scan(&existingCuis)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return fmt.Errorf("consultar cuis existente: %w", err)
|
||||
}
|
||||
|
||||
// 5. Insertar o actualizar
|
||||
if err == sql.ErrNoRows {
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO cuis (registro_empresa_id, cuis) VALUES ($1, $2)`, empresaID, nuevoCuis)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insertar nuevo cuis: %w", err)
|
||||
}
|
||||
} else if existingCuis != nuevoCuis {
|
||||
_, err = tx.ExecContext(ctx, `UPDATE cuis SET cuis=$1, fecha_vigencia=$2, fecha_actualizacion=$3 WHERE id=$4`,
|
||||
nuevoCuis, fechaVigencia, currentBolivia, cuisID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("actualizar cuis existente: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Confirmar tx
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("confirmar transacción: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
100
daemonService/internal/services/obtencionCodigo/modoProceso.go
Normal file
100
daemonService/internal/services/obtencionCodigo/modoProceso.go
Normal file
@ -0,0 +1,100 @@
|
||||
package obtencionCodigo
|
||||
|
||||
//import (
|
||||
// "context"
|
||||
// "daemonService/internal/models/obtencionCodigos/request"
|
||||
// "encoding/json"
|
||||
// "net/http"
|
||||
//)
|
||||
//
|
||||
//// procesa la ejecución en modo cron
|
||||
//func (s *ObtencionCodigoService) processCronMode(dbCtx context.Context) (interface{}, error) {
|
||||
// empresasConCuis, err := s.GetEmpresasConCuisMinimal(dbCtx)
|
||||
// if err != nil {
|
||||
// return nil, s.LogError("Error al obtener empresas con CUIS: %v", err)
|
||||
// }
|
||||
//
|
||||
// var lastResponse interface{}
|
||||
// for _, ec := range empresasConCuis {
|
||||
// s.LogMessage("Procesando empresa ID: %d", ec.ID)
|
||||
//
|
||||
// if len(ec.Cuis) == 0 {
|
||||
// s.LogMessage("Empresa ID %d sin CUIS registrados", ec.ID)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// for _, c := range ec.Cuis {
|
||||
// s.LogMessage("Procesando CUIS ID: %d", c.Cuis_id)
|
||||
//
|
||||
// cuisRequest := s.buildCuisRequestFromEmpresa(ec)
|
||||
// respCuis, err := codigoProcesarRegistro(&s.CuisServiceModel, cuisRequest, 0)
|
||||
//
|
||||
// if err != nil {
|
||||
// s.LogError(logErrorProcessing, err, cuisRequest.CodigoSistema)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// if err := s.actualizarRegistro(dbCtx, ec.ID, c.Cuis_id, cuisRequest, respCuis); err != nil {
|
||||
// s.LogError("Error actualizando registro: %v", err)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// lastResponse = respCuis
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return lastResponse, nil
|
||||
//}
|
||||
//
|
||||
//// procesa la ejecución en modo API
|
||||
//func (s *ObtencionCodigoService) processAPIMode(dbCtx context.Context, w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
// cuisRequest, err := s.parseCuisRequest(w, req)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if err := s.validateCuisRequest(cuisRequest); err != nil {
|
||||
// return nil, s.LogError("Datos inválidos: %v", err)
|
||||
// }
|
||||
//
|
||||
// respCuis, err := codigoProcesarRegistro(&s.CuisServiceModel, cuisRequest, 0)
|
||||
// if err != nil {
|
||||
// return nil, s.LogError(logErrorProcessing, err, cuisRequest.CodigoSistema)
|
||||
// }
|
||||
//
|
||||
// if err := s.crearNuevoRegistro(dbCtx, cuisRequest, respCuis); err != nil {
|
||||
// return nil, s.LogError("Error creando nuevo registro: %v", err)
|
||||
// }
|
||||
//
|
||||
// return respCuis, nil
|
||||
//}
|
||||
//
|
||||
//// parsea la solicitud de CUIS desde el body del request
|
||||
//func (s *ObtencionCodigoService) parseCuisRequest(w http.ResponseWriter, req *http.Request) (request.SolicitudCuis, error) {
|
||||
// var cuisRequest request.SolicitudCuis
|
||||
//
|
||||
// // Limitar el tamaño del body para prevenir ataques DoS
|
||||
// req.Body = http.MaxBytesReader(w, req.Body, maxBodySize)
|
||||
//
|
||||
// decoder := json.NewDecoder(req.Body)
|
||||
// decoder.DisallowUnknownFields() // Rechazar campos desconocidos
|
||||
//
|
||||
// if err := decoder.Decode(&cuisRequest); err != nil {
|
||||
// return cuisRequest, s.LogError("Error al decodificar JSON: %v", err)
|
||||
// }
|
||||
//
|
||||
// return cuisRequest, nil
|
||||
//}
|
||||
//
|
||||
//// valida los datos de la solicitud CUIS
|
||||
//func (s *ObtencionCodigoService) validateCuisRequest(request request.SolicitudCuis) error {
|
||||
// if request.CodigoAmbiente == "" ||
|
||||
// request.CodigoModalidad == "" ||
|
||||
// request.CodigoPuntoVenta == "" ||
|
||||
// request.CodigoSistema == "" ||
|
||||
// request.CodigoSucursal == "" ||
|
||||
// request.Nit == "" {
|
||||
// return ErrAllFieldsRequired
|
||||
// }
|
||||
// return nil
|
||||
//}
|
@ -0,0 +1,715 @@
|
||||
// // domain/models.go
|
||||
package obtencionCodigo
|
||||
|
||||
//
|
||||
//import (
|
||||
// "time"
|
||||
//)
|
||||
//
|
||||
//// RegistroEmpresa mapea toda la tabla registroEmpresa
|
||||
//type RegistroEmpresa struct {
|
||||
// ID int `sql:"id"`
|
||||
// CodigoAmbiente int `sql:"codigo_ambiente"`
|
||||
// CodigoModalidad int `sql:"codigo_modalidad"`
|
||||
// CodigoPuntoVenta int `sql:"codigo_punto_venta"`
|
||||
// CodigoSistema string `sql:"codigo_sistema"`
|
||||
// CodigoSucursal int `sql:"codigo_sucursal"`
|
||||
// Nit string `sql:"nit"`
|
||||
// FechaCreacion time.Time `sql:"fecha_creacion"`
|
||||
// FechaActualizacion time.Time `sql:"fecha_actualizacion"`
|
||||
//}
|
||||
//
|
||||
//// CuisMinimal solo tiene los dos campos que quieres de la tabla cuis
|
||||
//type CuisMinimal struct {
|
||||
// Cuis_id int64 `sql:"id"`
|
||||
// Cuis string `sql:"cuis"`
|
||||
// FechaVigencia time.Time `sql:"fecha_vigencia"`
|
||||
//}
|
||||
//
|
||||
//// EmpresaConCuis agrupa una empresa con sus cuis mínimos
|
||||
//type EmpresaConCuis struct {
|
||||
// RegistroEmpresa
|
||||
// Cuis []CuisMinimal
|
||||
//}
|
||||
|
||||
//// domain/errors.go
|
||||
//package domain
|
||||
//
|
||||
//import "errors"
|
||||
//
|
||||
//// Errores comunes
|
||||
//var (
|
||||
// ErrAllFieldsRequired = errors.New("todos los campos son requeridos")
|
||||
// ErrUnexpectedRespType = errors.New("tipo de respuesta inesperado")
|
||||
// ErrDuplicateRecord = errors.New("ya existe un registro con el mismo NIT, código sucursal y código punto de venta")
|
||||
//)
|
||||
|
||||
//// utils/constants.go
|
||||
//package utils
|
||||
//
|
||||
//// Constantes para la configuración general
|
||||
//const (
|
||||
// MaxBodySize = 1 * 1024 * 1024 // 1MB
|
||||
// DefaultTimeZone = "America/La_Paz"
|
||||
// LogServiceStart = "Iniciando servicio %s"
|
||||
// LogServiceEnd = "Finalizado servicio %s"
|
||||
// LogErrorProcessing = "Error procesando registro %s: %v"
|
||||
//)
|
||||
|
||||
//// utils/time_utils.go
|
||||
//package utils
|
||||
//
|
||||
//import (
|
||||
//"time"
|
||||
//)
|
||||
//
|
||||
//// GetCurrentBoliviaTime obtiene la hora actual en la zona horaria de Bolivia
|
||||
//func GetCurrentBoliviaTime() (time.Time, error) {
|
||||
// loc, err := time.LoadLocation(DefaultTimeZone)
|
||||
// if err != nil {
|
||||
// return time.Time{}, err
|
||||
// }
|
||||
// return time.Now().In(loc), nil
|
||||
//}
|
||||
|
||||
//// utils/soap_utils.go
|
||||
//package utils
|
||||
//
|
||||
//import (
|
||||
//"fmt"
|
||||
//
|
||||
//"daemonService/internal/models/obtencionCodigos/response"
|
||||
//"obtencionCodigo/domain"
|
||||
//)
|
||||
//
|
||||
//// ParseSoapCuisResponse parsea la respuesta SOAP para CUIS
|
||||
//func ParseSoapCuisResponse(respSoap interface{}) (string, string, bool, error) {
|
||||
// resp, ok := respSoap.(response.SoapBodyCuis)
|
||||
// if !ok {
|
||||
// return "", "", false, fmt.Errorf("%w: %T", domain.ErrUnexpectedRespType, respSoap)
|
||||
// }
|
||||
//
|
||||
// return resp.Response.Respuesta.Codigo,
|
||||
// resp.Response.Respuesta.FechaVigencia,
|
||||
// resp.Response.Respuesta.Transaccion,
|
||||
// nil
|
||||
//}
|
||||
|
||||
// repository/empresa_repo.go
|
||||
//package repository
|
||||
//
|
||||
//import (
|
||||
//"context"
|
||||
//"database/sql"
|
||||
//"fmt"
|
||||
//"time"
|
||||
//
|
||||
//"obtencionCodigo/domain"
|
||||
//"daemonService/internal/models/obtencionCodigos/request"
|
||||
//)
|
||||
//
|
||||
//type EmpresaRepository struct {
|
||||
// DB *sql.DB
|
||||
//}
|
||||
//
|
||||
//// GetEmpresasConCuisMinimal obtiene todas las empresas con sus CUIS
|
||||
//func (r *EmpresaRepository) GetEmpresasConCuisMinimal(ctx context.Context) ([]domain.EmpresaConCuis, error) {
|
||||
// const query = `
|
||||
// SELECT
|
||||
// -- columnas de registroEmpresa
|
||||
// re.id, re.codigo_ambiente, re.codigo_modalidad, re.codigo_punto_venta,
|
||||
// re.codigo_sistema, re.codigo_sucursal, re.nit,
|
||||
// re.fecha_creacion, re.fecha_actualizacion,
|
||||
// -- columnas específicas de cuis
|
||||
// c.id, c.cuis, c.fecha_vigencia
|
||||
// FROM registroEmpresa re
|
||||
// LEFT JOIN cuis c ON c.registro_empresa_id = re.id
|
||||
// ORDER BY re.id;
|
||||
// `
|
||||
//
|
||||
// rows, err := r.DB.QueryContext(ctx, query)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("consulta empresas con cuis minimal: %w", err)
|
||||
// }
|
||||
// defer rows.Close()
|
||||
//
|
||||
// // Mapa temporal para agrupar por empresa.ID
|
||||
// empresasMap := make(map[int]*domain.EmpresaConCuis)
|
||||
//
|
||||
// for rows.Next() {
|
||||
// // Variables temporales de escaneo
|
||||
// var (
|
||||
// id int
|
||||
// codigoAmbiente int
|
||||
// codigoModalidad int
|
||||
// codigoPuntoVenta int
|
||||
// codigoSistema string
|
||||
// codigoSucursal int
|
||||
// nit string
|
||||
// fechaCreacion time.Time
|
||||
// fechaActualizacion time.Time
|
||||
//
|
||||
// idCuis sql.NullInt64
|
||||
// cuis sql.NullString
|
||||
// fechaVigencia sql.NullTime
|
||||
// )
|
||||
//
|
||||
// // Leer la fila
|
||||
// if err := rows.Scan(
|
||||
// &id, &codigoAmbiente, &codigoModalidad, &codigoPuntoVenta,
|
||||
// &codigoSistema, &codigoSucursal, &nit,
|
||||
// &fechaCreacion, &fechaActualizacion,
|
||||
// &idCuis, &cuis, &fechaVigencia,
|
||||
// ); err != nil {
|
||||
// return nil, fmt.Errorf("scan fila: %w", err)
|
||||
// }
|
||||
//
|
||||
// // ¿Ya existe la empresa en el mapa?
|
||||
// ent, ok := empresasMap[id]
|
||||
// if !ok {
|
||||
// // Si no existe, la creamos y rellenamos datos de registroEmpresa
|
||||
// ent = &domain.EmpresaConCuis{
|
||||
// RegistroEmpresa: domain.RegistroEmpresa{
|
||||
// ID: id,
|
||||
// CodigoAmbiente: codigoAmbiente,
|
||||
// CodigoModalidad: codigoModalidad,
|
||||
// CodigoPuntoVenta: codigoPuntoVenta,
|
||||
// CodigoSistema: codigoSistema,
|
||||
// CodigoSucursal: codigoSucursal,
|
||||
// Nit: nit,
|
||||
// FechaCreacion: fechaCreacion,
|
||||
// FechaActualizacion: fechaActualizacion,
|
||||
// },
|
||||
// Cuis: make([]domain.CuisMinimal, 0, 1),
|
||||
// }
|
||||
// empresasMap[id] = ent
|
||||
// }
|
||||
//
|
||||
// // Si hay un CUIS válido, lo añadimos al slice
|
||||
// if idCuis.Valid && cuis.Valid && fechaVigencia.Valid {
|
||||
// ent.Cuis = append(ent.Cuis, domain.CuisMinimal{
|
||||
// Cuis_id: idCuis.Int64,
|
||||
// Cuis: cuis.String,
|
||||
// FechaVigencia: fechaVigencia.Time,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if err := rows.Err(); err != nil {
|
||||
// return nil, fmt.Errorf("iteración filas: %w", err)
|
||||
// }
|
||||
//
|
||||
// // Convertimos el mapa a slice ordenado por aparición
|
||||
// resp := make([]domain.EmpresaConCuis, 0, len(empresasMap))
|
||||
// for _, ent := range empresasMap {
|
||||
// resp = append(resp, *ent)
|
||||
// }
|
||||
// return resp, nil
|
||||
//}
|
||||
//
|
||||
//// BuscarRegistroEmpresa busca un registro de empresa por sus criterios
|
||||
//func (r *EmpresaRepository) BuscarRegistroEmpresa(ctx context.Context, request request.SolicitudCuis) (int, error) {
|
||||
// query := `
|
||||
// SELECT id
|
||||
// FROM registroEmpresa
|
||||
// WHERE codigo_sistema = $1
|
||||
// AND nit = $2
|
||||
// AND codigo_ambiente = $3
|
||||
// AND codigo_punto_venta = $4
|
||||
// AND codigo_sucursal = $5
|
||||
// LIMIT 1
|
||||
// `
|
||||
//
|
||||
// var id int
|
||||
// err := r.DB.QueryRowContext(
|
||||
// ctx,
|
||||
// query,
|
||||
// request.CodigoSistema,
|
||||
// request.Nit,
|
||||
// request.CodigoAmbiente,
|
||||
// request.CodigoPuntoVenta,
|
||||
// request.CodigoSucursal,
|
||||
// ).Scan(&id)
|
||||
//
|
||||
// if err == sql.ErrNoRows {
|
||||
// return 0, nil // No existe el registro
|
||||
// }
|
||||
//
|
||||
// if err != nil {
|
||||
// return 0, err
|
||||
// }
|
||||
//
|
||||
// return id, nil
|
||||
//}
|
||||
|
||||
//// repository/cuis_repo.go
|
||||
//package repository
|
||||
//
|
||||
//import (
|
||||
//"context"
|
||||
//"database/sql"
|
||||
//"fmt"
|
||||
//"time"
|
||||
//
|
||||
//"obtencionCodigo/domain"
|
||||
//"obtencionCodigo/utils"
|
||||
//"daemonService/internal/models/obtencionCodigos/request"
|
||||
//)
|
||||
//
|
||||
//type CuisRepository struct {
|
||||
// DB *sql.DB
|
||||
//}
|
||||
//
|
||||
//// CrearNuevoRegistro crea un nuevo registro de empresa y CUIS
|
||||
//func (r *CuisRepository) CrearNuevoRegistro(ctx context.Context, request request.SolicitudCuis, codigoCuis string, fechaVigencia string, transaccion bool) error {
|
||||
// currentBolivia, err := utils.GetCurrentBoliviaTime()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // Iniciar transacción
|
||||
// tx, err := r.DB.BeginTx(ctx, nil)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al iniciar transacción: %w", err)
|
||||
// }
|
||||
// defer tx.Rollback()
|
||||
//
|
||||
// // Verificar si ya existe el NIT en la base de datos
|
||||
// var existeNIT bool
|
||||
// queryVerificacionNIT := `
|
||||
// SELECT EXISTS (
|
||||
// SELECT 1 FROM registroEmpresa
|
||||
// WHERE nit = $1
|
||||
// )
|
||||
// `
|
||||
// err = tx.QueryRowContext(
|
||||
// ctx,
|
||||
// queryVerificacionNIT,
|
||||
// request.Nit,
|
||||
// ).Scan(&existeNIT)
|
||||
//
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al verificar existencia del NIT: %w", err)
|
||||
// }
|
||||
//
|
||||
// // Si el NIT ya existe, verificar si la combinación de sucursal y punto de venta existe
|
||||
// if existeNIT {
|
||||
// var existeCombinacion bool
|
||||
// queryVerificacionCombinacion := `
|
||||
// SELECT EXISTS (
|
||||
// SELECT 1 FROM registroEmpresa
|
||||
// WHERE nit = $1
|
||||
// AND codigo_sucursal = $2
|
||||
// AND codigo_punto_venta = $3
|
||||
// )
|
||||
// `
|
||||
// err = tx.QueryRowContext(
|
||||
// ctx,
|
||||
// queryVerificacionCombinacion,
|
||||
// request.Nit,
|
||||
// request.CodigoSucursal,
|
||||
// request.CodigoPuntoVenta,
|
||||
// ).Scan(&existeCombinacion)
|
||||
//
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al verificar combinación de sucursal y punto de venta: %w", err)
|
||||
// }
|
||||
//
|
||||
// if existeCombinacion {
|
||||
// return domain.ErrDuplicateRecord
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Insertar en registroEmpresa
|
||||
// query := `
|
||||
// INSERT INTO registroEmpresa (
|
||||
// codigo_ambiente,
|
||||
// codigo_modalidad,
|
||||
// codigo_punto_venta,
|
||||
// codigo_sistema,
|
||||
// codigo_sucursal,
|
||||
// nit
|
||||
// ) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
// RETURNING id
|
||||
// `
|
||||
// var registroID int
|
||||
// err = tx.QueryRowContext(
|
||||
// ctx,
|
||||
// query,
|
||||
// request.CodigoAmbiente,
|
||||
// request.CodigoModalidad,
|
||||
// request.CodigoPuntoVenta,
|
||||
// request.CodigoSistema,
|
||||
// request.CodigoSucursal,
|
||||
// request.Nit,
|
||||
// ).Scan(®istroID)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al insertar registro: %w", err)
|
||||
// }
|
||||
//
|
||||
// // Insertar en cuis
|
||||
// queryInsertCuis := `
|
||||
// INSERT INTO cuis (cuis, fecha_vigencia, transaccion, fecha_creacion, registro_empresa_id)
|
||||
// VALUES ($1, $2, $3, $4, $5)
|
||||
// `
|
||||
// _, err = tx.ExecContext(ctx, queryInsertCuis, codigoCuis, fechaVigencia, transaccion, currentBolivia, registroID)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al insertar CUIS: %w", err)
|
||||
// }
|
||||
//
|
||||
// if err = tx.Commit(); err != nil {
|
||||
// return fmt.Errorf("error al confirmar transacción: %w", err)
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//// ActualizarRegistro actualiza un registro existente de empresa y CUIS
|
||||
//func (r *CuisRepository) ActualizarRegistro(ctx context.Context, empresaID int, cuisID int64, req request.SolicitudCuis, nuevoCuis string, fechaVigencia string) error {
|
||||
// currentBolivia, err := utils.GetCurrentBoliviaTime()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // Iniciar tx
|
||||
// tx, err := r.DB.BeginTx(ctx, nil)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("iniciar transacción: %w", err)
|
||||
// }
|
||||
// defer tx.Rollback()
|
||||
//
|
||||
// // Actualizar empresa
|
||||
// _, err = tx.ExecContext(ctx, `
|
||||
// UPDATE registroEmpresa
|
||||
// SET codigo_ambiente=$1, codigo_modalidad=$2, codigo_punto_venta=$3,
|
||||
// codigo_sistema=$4, codigo_sucursal=$5, nit=$6, fecha_actualizacion=$7
|
||||
// WHERE id=$8
|
||||
// `,
|
||||
// req.CodigoAmbiente, req.CodigoModalidad, req.CodigoPuntoVenta,
|
||||
// req.CodigoSistema, req.CodigoSucursal, req.Nit, currentBolivia, empresaID,
|
||||
// )
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("actualizar registroEmpresa: %w", err)
|
||||
// }
|
||||
//
|
||||
// // Comprobar si hay un CUIS existente
|
||||
// var existingCuis string
|
||||
// err = tx.QueryRowContext(ctx, `SELECT cuis FROM cuis WHERE id=$1 AND registro_empresa_id=$2`, cuisID, empresaID).Scan(&existingCuis)
|
||||
// if err != nil && err != sql.ErrNoRows {
|
||||
// return fmt.Errorf("consultar cuis existente: %w", err)
|
||||
// }
|
||||
//
|
||||
// // Insertar o actualizar
|
||||
// if err == sql.ErrNoRows {
|
||||
// _, err = tx.ExecContext(ctx, `INSERT INTO cuis (registro_empresa_id, cuis) VALUES ($1, $2)`, empresaID, nuevoCuis)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("insertar nuevo cuis: %w", err)
|
||||
// }
|
||||
// } else if existingCuis != nuevoCuis {
|
||||
// _, err = tx.ExecContext(ctx, `UPDATE cuis SET cuis=$1, fecha_vigencia=$2, fecha_actualizacion=$3 WHERE id=$4`,
|
||||
// nuevoCuis, fechaVigencia, currentBolivia, cuisID)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("actualizar cuis existente: %w", err)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Confirmar tx
|
||||
// if err := tx.Commit(); err != nil {
|
||||
// return fmt.Errorf("confirmar transacción: %w", err)
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
|
||||
//// service/cuis_service.go
|
||||
//package service
|
||||
//
|
||||
//import (
|
||||
//"context"
|
||||
//"fmt"
|
||||
//"log"
|
||||
//"strconv"
|
||||
//
|
||||
//"obtencionCodigo/domain"
|
||||
//"obtencionCodigo/repository"
|
||||
//"obtencionCodigo/utils"
|
||||
//"daemonService/internal/models/obtencionCodigos"
|
||||
//"daemonService/internal/models/obtencionCodigos/request"
|
||||
//)
|
||||
//
|
||||
//// Función externa que se supone que existe en el proyecto original
|
||||
//// Esta es una aproximación, ya que no tenemos el código fuente
|
||||
//var codigoProcesarRegistro = func(model *obtencionCodigos.CuisServiceModel, request request.SolicitudCuis, intentos int) (interface{}, error) {
|
||||
// // Esta función debe implementarse según el código original
|
||||
// return nil, nil
|
||||
//}
|
||||
//
|
||||
//type CuisService struct {
|
||||
// Model *obtencionCodigos.CuisServiceModel
|
||||
// EmpresaRepo *repository.EmpresaRepository
|
||||
// CuisRepo *repository.CuisRepository
|
||||
// Logger *log.Logger
|
||||
//}
|
||||
//
|
||||
//// GetName retorna el nombre del servicio
|
||||
//func (s *CuisService) GetName() string {
|
||||
// return s.Model.Name
|
||||
//}
|
||||
//
|
||||
//// logMessage registra un mensaje con el prefijo del servicio
|
||||
//func (s *CuisService) logMessage(format string, v ...interface{}) {
|
||||
// if s.Logger != nil {
|
||||
// s.Logger.Printf(format, v...)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// logError registra un error y devuelve el mismo error
|
||||
//func (s *CuisService) logError(format string, err error, v ...interface{}) error {
|
||||
// args := append([]interface{}{err}, v...)
|
||||
// if s.Logger != nil {
|
||||
// s.Logger.Printf(format, args...)
|
||||
// }
|
||||
// return fmt.Errorf(format, args...)
|
||||
//}
|
||||
//
|
||||
//// ValidateCuisRequest valida los datos de la solicitud CUIS
|
||||
//func (s *CuisService) ValidateCuisRequest(request request.SolicitudCuis) error {
|
||||
// if request.CodigoAmbiente == "" ||
|
||||
// request.CodigoModalidad == "" ||
|
||||
// request.CodigoPuntoVenta == "" ||
|
||||
// request.CodigoSistema == "" ||
|
||||
// request.CodigoSucursal == "" ||
|
||||
// request.Nit == "" {
|
||||
// return domain.ErrAllFieldsRequired
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//// BuildCuisRequestFromEmpresa crea una solicitud CUIS a partir de datos de empresa
|
||||
//func (s *CuisService) BuildCuisRequestFromEmpresa(ec domain.EmpresaConCuis) request.SolicitudCuis {
|
||||
// return request.SolicitudCuis{
|
||||
// CodigoAmbiente: strconv.Itoa(ec.CodigoAmbiente),
|
||||
// CodigoModalidad: strconv.Itoa(ec.CodigoModalidad),
|
||||
// CodigoPuntoVenta: strconv.Itoa(ec.CodigoPuntoVenta),
|
||||
// CodigoSistema: ec.CodigoSistema,
|
||||
// CodigoSucursal: strconv.Itoa(ec.CodigoSucursal),
|
||||
// Nit: ec.Nit,
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// ProcessEmpresa procesa una empresa y actualiza su CUIS si es necesario
|
||||
//func (s *CuisService) ProcessEmpresa(ctx context.Context, ec domain.EmpresaConCuis) (interface{}, error) {
|
||||
// s.logMessage("Procesando empresa ID: %d", ec.ID)
|
||||
//
|
||||
// if len(ec.Cuis) == 0 {
|
||||
// s.logMessage("Empresa ID %d sin CUIS registrados", ec.ID)
|
||||
// return nil, nil
|
||||
// }
|
||||
//
|
||||
// var lastResponse interface{}
|
||||
// for _, c := range ec.Cuis {
|
||||
// s.logMessage("Procesando CUIS ID: %d", c.Cuis_id)
|
||||
//
|
||||
// cuisRequest := s.BuildCuisRequestFromEmpresa(ec)
|
||||
// respCuis, err := codigoProcesarRegistro(s.Model, cuisRequest, 0)
|
||||
//
|
||||
// if err != nil {
|
||||
// s.logError(utils.LogErrorProcessing, err, cuisRequest.CodigoSistema)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// // Parsear respuesta SOAP
|
||||
// nuevoCuis, fechaVigencia, transaccion, err := utils.ParseSoapCuisResponse(respCuis)
|
||||
// if err != nil {
|
||||
// s.logError("Error al parsear respuesta SOAP: %v", err)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// if err := s.CuisRepo.ActualizarRegistro(ctx, ec.ID, c.Cuis_id, cuisRequest, nuevoCuis, fechaVigencia); err != nil {
|
||||
// s.logError("Error actualizando registro: %v", err)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// lastResponse = respCuis
|
||||
// }
|
||||
//
|
||||
// return lastResponse, nil
|
||||
//}
|
||||
//
|
||||
//// handler/api_handler.go
|
||||
//package handler
|
||||
//
|
||||
//import (
|
||||
//"context"
|
||||
//"encoding/json"
|
||||
//"fmt"
|
||||
//"net/http"
|
||||
//
|
||||
//"obtencionCodigo/service"
|
||||
//"obtencionCodigo/utils"
|
||||
//"daemonService/internal/models/obtencionCodigos/request"
|
||||
//)
|
||||
//
|
||||
//type APIHandler struct {
|
||||
// CuisService *service.CuisService
|
||||
//}
|
||||
//
|
||||
//// ParseCuisRequest parsea la solicitud de CUIS desde el body del request
|
||||
//func (h *APIHandler) ParseCuisRequest(w http.ResponseWriter, req *http.Request) (request.SolicitudCuis, error) {
|
||||
// var cuisRequest request.SolicitudCuis
|
||||
//
|
||||
// // Limitar el tamaño del body para prevenir ataques DoS
|
||||
// req.Body = http.MaxBytesReader(w, req.Body, utils.MaxBodySize)
|
||||
//
|
||||
// decoder := json.NewDecoder(req.Body)
|
||||
// decoder.DisallowUnknownFields() // Rechazar campos desconocidos
|
||||
//
|
||||
// if err := decoder.Decode(&cuisRequest); err != nil {
|
||||
// return cuisRequest, fmt.Errorf("error al decodificar JSON: %v", err)
|
||||
// }
|
||||
//
|
||||
// return cuisRequest, nil
|
||||
//}
|
||||
//
|
||||
//// HandleAPIRequest maneja las solicitudes API
|
||||
//func (h *APIHandler) HandleAPIRequest(ctx context.Context, w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
// // Parsear la solicitud
|
||||
// cuisRequest, err := h.ParseCuisRequest(w, req)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// // Validar la solicitud
|
||||
// if err := h.CuisService.ValidateCuisRequest(cuisRequest); err != nil {
|
||||
// return nil, fmt.Errorf("datos inválidos: %v", err)
|
||||
// }
|
||||
//
|
||||
// // Procesar la solicitud
|
||||
// respCuis, err := codigoProcesarRegistro(h.CuisService.Model, cuisRequest, 0)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf(utils.LogErrorProcessing, cuisRequest.CodigoSistema, err)
|
||||
// }
|
||||
//
|
||||
// // Parsear respuesta SOAP
|
||||
// codigoCuis, fechaVigencia, transaccion, err := utils.ParseSoapCuisResponse(respCuis)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("error al parsear respuesta SOAP: %v", err)
|
||||
// }
|
||||
//
|
||||
// // Crear nuevo registro
|
||||
// if err := h.CuisService.CuisRepo.CrearNuevoRegistro(ctx, cuisRequest, codigoCuis, fechaVigencia, transaccion); err != nil {
|
||||
// return nil, fmt.Errorf("error creando nuevo registro: %v", err)
|
||||
// }
|
||||
//
|
||||
// return respCuis, nil
|
||||
//}
|
||||
|
||||
//// handler/cron_handler.go
|
||||
//package handler
|
||||
//
|
||||
//import (
|
||||
//"context"
|
||||
//
|
||||
//"obtencionCodigo/service"
|
||||
//)
|
||||
//
|
||||
//type CronHandler struct {
|
||||
// CuisService *service.CuisService
|
||||
//}
|
||||
//
|
||||
//// HandleCronRequest maneja las ejecuciones programadas
|
||||
//func (h *CronHandler) HandleCronRequest(ctx context.Context) (interface{}, error) {
|
||||
// // Obtener todas las empresas con sus CUIS
|
||||
// empresasConCuis, err := h.CuisService.EmpresaRepo.GetEmpresasConCuisMinimal(ctx)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("error al obtener empresas con CUIS: %v", err)
|
||||
// }
|
||||
//
|
||||
// var lastResponse interface{}
|
||||
// // Procesar cada empresa
|
||||
// for _, ec := range empresasConCuis {
|
||||
// resp, err := h.CuisService.ProcessEmpresa(ctx, ec)
|
||||
// if err != nil {
|
||||
// h.CuisService.logError("Error procesando empresa %d: %v", err, ec.ID)
|
||||
// continue
|
||||
// }
|
||||
// if resp != nil {
|
||||
// lastResponse = resp
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return lastResponse, nil
|
||||
//}
|
||||
|
||||
//// service.go (punto de entrada principal)
|
||||
//package obtencionCodigo
|
||||
//
|
||||
//import (
|
||||
//"context"
|
||||
//"log"
|
||||
//"net/http"
|
||||
//
|
||||
//"obtencionCodigo/domain"
|
||||
//"obtencionCodigo/repository"
|
||||
//"obtencionCodigo/service"
|
||||
//"obtencionCodigo/handler"
|
||||
//"obtencionCodigo/utils"
|
||||
//"daemonService/internal/models/obtencionCodigos"
|
||||
//)
|
||||
//
|
||||
//type ObtencionCodigoService struct {
|
||||
// Model obtencionCodigos.CuisServiceModel
|
||||
// Logger *log.Logger
|
||||
// EmpresaRepo *repository.EmpresaRepository
|
||||
// CuisRepo *repository.CuisRepository
|
||||
// CuisService *service.CuisService
|
||||
// APIHandler *handler.APIHandler
|
||||
// CronHandler *handler.CronHandler
|
||||
//}
|
||||
//
|
||||
//// NewObtencionCodigoService crea una nueva instancia del servicio
|
||||
//func NewObtencionCodigoService(model obtencionCodigos.CuisServiceModel, logger *log.Logger) *ObtencionCodigoService {
|
||||
// empresaRepo := &repository.EmpresaRepository{DB: model.DB}
|
||||
// cuisRepo := &repository.CuisRepository{DB: model.DB}
|
||||
//
|
||||
// cuisService := &service.CuisService{
|
||||
// Model: &model,
|
||||
// EmpresaRepo: empresaRepo,
|
||||
// CuisRepo: cuisRepo,
|
||||
// Logger: logger,
|
||||
// }
|
||||
//
|
||||
// return &ObtencionCodigoService{
|
||||
// Model: model,
|
||||
// Logger: logger,
|
||||
// EmpresaRepo: empresaRepo,
|
||||
// CuisRepo: cuisRepo,
|
||||
// CuisService: cuisService,
|
||||
// APIHandler: &handler.APIHandler{CuisService: cuisService},
|
||||
// CronHandler: &handler.CronHandler{CuisService: cuisService},
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// GetName retorna el nombre del servicio
|
||||
//func (s *ObtencionCodigoService) GetName() string {
|
||||
// return s.Model.Name
|
||||
//}
|
||||
//
|
||||
//// ExecuteGetCode maneja la obtención de códigos CUIS
|
||||
//func (s *ObtencionCodigoService) ExecuteGetCode(dbCtx context.Context, w http.ResponseWriter, req *http.Request, isCron bool) (interface{}, error) {
|
||||
// s.Logger.Printf(utils.LogServiceStart, s.GetName())
|
||||
// defer s.Logger.Printf(utils.LogServiceEnd, s.GetName())
|
||||
//
|
||||
// var respCuis interface{}
|
||||
// var err error
|
||||
//
|
||||
// if isCron {
|
||||
// respCuis, err = s.CronHandler.HandleCronRequest(dbCtx)
|
||||
// } else {
|
||||
// respCuis, err = s.APIHandler.HandleAPIRequest(dbCtx, w, req)
|
||||
// }
|
||||
//
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return respCuis, nil
|
||||
//}
|
@ -0,0 +1,54 @@
|
||||
package obtencionCodigo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/handlers"
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
service "daemonService/internal/services"
|
||||
"daemonService/internal/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// maneja la obtención de CUIS
|
||||
type ObtencionCodigoService struct {
|
||||
Model obtencionCodigos.CuisServiceModel
|
||||
CuisService *service.CuisService
|
||||
CronHandler *handlers.CronHandler
|
||||
ApiHandler *handlers.APIHandler
|
||||
}
|
||||
|
||||
// crea una nueva instancia del servicio
|
||||
func NewObtencionCodigoService(
|
||||
model obtencionCodigos.CuisServiceModel, cuisService *service.CuisService,
|
||||
cronHandler *handlers.CronHandler, apiHandler *handlers.APIHandler,
|
||||
) *ObtencionCodigoService {
|
||||
return &ObtencionCodigoService{
|
||||
Model: model, CuisService: cuisService,
|
||||
CronHandler: cronHandler, ApiHandler: apiHandler,
|
||||
}
|
||||
}
|
||||
|
||||
// maneja la obtención de códigos CUIS
|
||||
func (s *ObtencionCodigoService) ExecuteGetCode(dbCtx context.Context, w http.ResponseWriter, req *http.Request, isCron bool) (interface{}, error) {
|
||||
s.CuisService.LogMessage(utils.LogServiceStart, s.GetName())
|
||||
defer s.CuisService.LogMessage(utils.LogServiceEnd, s.GetName())
|
||||
|
||||
var respCuis interface{}
|
||||
var err error
|
||||
|
||||
if isCron {
|
||||
respCuis, err = s.CronHandler.ProcessCronMode(dbCtx)
|
||||
} else {
|
||||
respCuis, err = s.ApiHandler.HandleAPIRequest(dbCtx, w, req)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return respCuis, nil
|
||||
}
|
||||
|
||||
// GetName retorna el nombre del servicio
|
||||
func (s *ObtencionCodigoService) GetName() string {
|
||||
return s.Model.Name
|
||||
}
|
@ -0,0 +1,851 @@
|
||||
package obtencionCodigo
|
||||
|
||||
//
|
||||
//import (
|
||||
// "context"
|
||||
// "daemonService/internal/handlers"
|
||||
// "daemonService/internal/models"
|
||||
// svc "daemonService/internal/models"
|
||||
// "daemonService/internal/models/obtencionCodigos"
|
||||
// "daemonService/internal/repositories"
|
||||
// "daemonService/internal/services/obtencionCodigo"
|
||||
// "daemonService/internal/services/procesar"
|
||||
// "daemonService/internal/services/sincronizacionDatos"
|
||||
// "flag"
|
||||
// "fmt"
|
||||
// "github.com/robfig/cron/v3"
|
||||
// "log"
|
||||
// "net/http"
|
||||
// "os"
|
||||
// "os/signal"
|
||||
// "syscall"
|
||||
// "time"
|
||||
//
|
||||
// "daemonService/internal/api"
|
||||
// "daemonService/internal/config"
|
||||
// "daemonService/internal/database"
|
||||
// "daemonService/internal/notifications"
|
||||
// "daemonService/internal/services"
|
||||
//)
|
||||
//
|
||||
//func main() {
|
||||
// configFile := flag.String("config", "configs/config.yaml", "Ruta al archivo de configuración")
|
||||
// runOnce := flag.Bool("run-once", false, "Ejecutar servicios una vez y salir")
|
||||
// serviceName := flag.String("service", "", "Nombre del servicio específico a ejecutar")
|
||||
// flag.Parse()
|
||||
//
|
||||
// // Cargar configuración
|
||||
// cfg, err := config.LoadConfig(*configFile)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Error al cargar configuración: %v", err)
|
||||
// }
|
||||
//
|
||||
// // Inicializar la base de datos
|
||||
// db, err := database.InitDB(*cfg)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Error al inicializar la base de datos: %v", err)
|
||||
// }
|
||||
// defer db.Close()
|
||||
//
|
||||
// // Crear sistema de notificaciones
|
||||
// notifier := notifications.CreateNotificationSender(cfg.Notifications.Enabled, cfg.Notifications.Method, cfg.Notifications.Config)
|
||||
// if notifier != nil {
|
||||
// log.Printf("Sistema de notificaciones iniciado: %s", cfg.Notifications.Method)
|
||||
// }
|
||||
//
|
||||
// // Crear servicios a partir de la configuración
|
||||
// //var cuisServices []models.CuisService
|
||||
// var servicesList []models.SoapService
|
||||
// var servicesList2 []obtencionCodigos.CuisService
|
||||
// for _, svcCfg := range cfg.Services {
|
||||
// if !svcCfg.Enabled {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// // Crear el logger para el servicio
|
||||
// serviceLogger := log.New(log.Writer(), fmt.Sprintf("[%s] ", svcCfg.Name), log.LstdFlags)
|
||||
//
|
||||
// log.Println(cfg.SOAP.FACTURA_SINCRONIZACION.Retries)
|
||||
// baseSvc := svc.ServiceModel{
|
||||
// Name: svcCfg.Name,
|
||||
// Enabled: svcCfg.Enabled,
|
||||
// Concurrency: svcCfg.Concurrency,
|
||||
// DB: db,
|
||||
// SOAPEndpoint: cfg.SOAP.FACTURA_SINCRONIZACION.Endpoint,
|
||||
// SOAPTimeout: time.Duration(cfg.SOAP.FACTURA_SINCRONIZACION.Timeout) * time.Second,
|
||||
// SOAPRetries: cfg.SOAP.FACTURA_SINCRONIZACION.Retries,
|
||||
// NotificationSender: notifier,
|
||||
// APIKey: cfg.SOAP.APIKey,
|
||||
// TagNames: "",
|
||||
// QueryInsert: "",
|
||||
// MsgCustomResponse: "",
|
||||
// }
|
||||
//
|
||||
// cuisServiceModel := obtencionCodigos.CuisServiceModel{
|
||||
// Name: svcCfg.Name,
|
||||
// Enabled: svcCfg.Enabled,
|
||||
// Concurrency: svcCfg.Concurrency,
|
||||
// DB: db,
|
||||
// SOAPEndpoint: cfg.SOAP.FACTURA_CODIGO.Endpoint,
|
||||
// SOAPTimeout: time.Duration(cfg.SOAP.FACTURA_CODIGO.Timeout) * time.Second,
|
||||
// SOAPRetries: cfg.SOAP.FACTURA_CODIGO.Retries,
|
||||
// NotificationSender: notifier,
|
||||
// APIKey: cfg.SOAP.APIKey,
|
||||
// TagNames: "",
|
||||
// QueryInsert: "",
|
||||
// MsgCustomResponse: "",
|
||||
// }
|
||||
//
|
||||
// //registrar-empresa solo para registrar empresa
|
||||
// cuisServiceModel.TagNames = "cuis"
|
||||
// cuisServiceModel.QueryInsert = "INSERT INTO registroEmpresa (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
// cuisServiceModel.MsgCustomResponse = "Registrar Empresa"
|
||||
//
|
||||
// procesarRegistro := procesar.NewProcesarRegistro(cuisServiceModel)
|
||||
// empresaRepository := repositories.NewEmpresaRepository(cuisServiceModel)
|
||||
// cuisService := services.NewCuisService(cuisServiceModel, serviceLogger)
|
||||
// cuisRepository := repositories.NewCuisRepository(baseSvc, cuisService)
|
||||
// cronHandler := handlers.NewCronHandler(cuisService, empresaRepository, cuisRepository, procesarRegistro)
|
||||
// apiHandler := handlers.NewApiHandler(cuisService, cuisRepository, procesarRegistro)
|
||||
//
|
||||
// // Llamar a NewObtencionCodigoService correctamente
|
||||
// obtencionCodigoSvc := obtencionCodigo.NewObtencionCodigoService(
|
||||
// cuisServiceModel,
|
||||
// cuisService,
|
||||
// cronHandler,
|
||||
// apiHandler,
|
||||
// )
|
||||
//
|
||||
// switch svcCfg.Name {
|
||||
// case "registrar-empresa":
|
||||
// servicesList2 = append(servicesList2, obtencionCodigoSvc)
|
||||
// break
|
||||
// case "leyendas_factura":
|
||||
// baseSvc.TagNames = "sincronizarListaLeyendasFactura"
|
||||
// baseSvc.QueryInsert = "INSERT INTO leyendas_factura (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
// baseSvc.MsgCustomResponse = "Listado Leyendas de Facturas"
|
||||
// servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
// ServiceModel: baseSvc,
|
||||
// })
|
||||
// case "producto-servicio":
|
||||
// baseSvc.TagNames = "sincronizarListaProductosServicios"
|
||||
// baseSvc.QueryInsert = "INSERT INTO productos_servicios (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
// baseSvc.MsgCustomResponse = "Listado Producto Servicio"
|
||||
// servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
// ServiceModel: baseSvc,
|
||||
// })
|
||||
// case "tipo_documento_identidad":
|
||||
// baseSvc.TagNames = "sincronizarParametricaTipoDocumentoIdentidad"
|
||||
// baseSvc.QueryInsert = "INSERT INTO tipo_documento_identidad (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
// baseSvc.MsgCustomResponse = "Listado tipos de documento de identidad"
|
||||
// servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
// ServiceModel: baseSvc,
|
||||
// })
|
||||
// case "tipo_documento_sector":
|
||||
// baseSvc.TagNames = "sincronizarParametricaTipoDocumentoSector"
|
||||
// baseSvc.QueryInsert = "INSERT INTO tipo_documento_sector (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
// baseSvc.MsgCustomResponse = "Listado tipos de documento de identidad"
|
||||
// servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
// ServiceModel: baseSvc,
|
||||
// })
|
||||
// case "tipo_metodo_pago":
|
||||
// baseSvc.TagNames = "sincronizarParametricaTipoMetodoPago"
|
||||
// baseSvc.QueryInsert = "INSERT INTO tipo_metodo_pago (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
// baseSvc.MsgCustomResponse = "Listado tipos metodo de pago"
|
||||
// servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
// ServiceModel: baseSvc,
|
||||
// })
|
||||
// case "tipo_moneda":
|
||||
// baseSvc.TagNames = "sincronizarParametricaTipoMoneda"
|
||||
// baseSvc.QueryInsert = "INSERT INTO tipo_moneda (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
// baseSvc.MsgCustomResponse = "Listado tipos de moneda"
|
||||
// servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
// ServiceModel: baseSvc,
|
||||
// })
|
||||
// case "tipo_punto_venta":
|
||||
// baseSvc.TagNames = "sincronizarParametricaTipoPuntoVenta"
|
||||
// baseSvc.QueryInsert = "INSERT INTO tipo_punto_venta (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
// baseSvc.MsgCustomResponse = "Listado tipos de punto de venta"
|
||||
// servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
// ServiceModel: baseSvc,
|
||||
// })
|
||||
// case "tipo_unidad_medida":
|
||||
// baseSvc.TagNames = "sincronizarParametricaUnidadMedida"
|
||||
// baseSvc.QueryInsert = "INSERT INTO tipo_unidad_medida (codigo, reqSoap, respSoap, reqJson, respJson, cuis_id, fecha_creacion) VALUES ($1, $2, $3, $4, $5, $6, NOW() AT TIME ZONE 'America/La_Paz') ON CONFLICT (codigo, cuis_id) DO UPDATE SET reqSoap = EXCLUDED.reqSoap, respSoap = EXCLUDED.respSoap, reqJson = EXCLUDED.reqJson, respJson = EXCLUDED.respJson, cuis_id = EXCLUDED.cuis_id, fecha_actualizacion = NOW() AT TIME ZONE 'America/La_Paz';"
|
||||
// baseSvc.MsgCustomResponse = "Listado tipos de unidad de medida"
|
||||
// servicesList = append(servicesList, &sincronizacionDatos.SincronizacionDatosService{
|
||||
// ServiceModel: baseSvc,
|
||||
// })
|
||||
// default:
|
||||
// log.Printf("Servicio desconocido: %s", svcCfg.Name)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Si se especifica un servicio específico, filtrar la lista.
|
||||
// if *serviceName != "" {
|
||||
// var filtered []models.SoapService
|
||||
// for _, s := range servicesList {
|
||||
// if s.GetName() == *serviceName {
|
||||
// filtered = append(filtered, s)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if len(filtered) == 0 {
|
||||
// log.Fatalf("No se encontró el servicio: %s", *serviceName)
|
||||
// }
|
||||
// servicesList = filtered
|
||||
// log.Printf("Ejecutando solo el servicio: %s", *serviceName)
|
||||
// }
|
||||
//
|
||||
// // Crear contexto con cancelación para manejar señales de terminación.
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel()
|
||||
//
|
||||
// // Canal para capturar señales del sistema.
|
||||
// sigChan := make(chan os.Signal, 1)
|
||||
// signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
//
|
||||
// // Iniciar servidor HTTP si se configura puerto para API.
|
||||
// var httpServer *http.Server
|
||||
// if cfg.API.Port > 0 {
|
||||
// httpServer = api.StartAPIServer(cfg.API.Port, servicesList, servicesList2, false)
|
||||
// }
|
||||
//
|
||||
// // Goroutine para manejar señales.
|
||||
// go func() {
|
||||
// sig := <-sigChan
|
||||
// log.Printf("Recibida señal: %v. Cerrando...", sig)
|
||||
// if notifier != nil {
|
||||
// notifier.SendNotification("Servicio detenido", fmt.Sprintf("El servicio fue detenido por la señal: %v", sig))
|
||||
// }
|
||||
// cancel()
|
||||
// if httpServer != nil {
|
||||
// log.Println("Cerrando servidor HTTP...")
|
||||
// shutdownCtx, cancelServer := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
// defer cancelServer()
|
||||
// if err := httpServer.Shutdown(shutdownCtx); err != nil {
|
||||
// log.Printf("Error al cerrar servidor HTTP: %v", err)
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// // Si se debe ejecutar solo una vez.
|
||||
// if *runOnce {
|
||||
// errors := services.RunAllServices(ctx, servicesList, false)
|
||||
// if len(errors) > 0 {
|
||||
// for _, e := range errors {
|
||||
// log.Printf("Error: %v", e)
|
||||
// }
|
||||
// os.Exit(1)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // Configurar primer cron
|
||||
// c := cron.New(cron.WithSeconds())
|
||||
// _, err = c.AddFunc(cfg.Schedule.FACTURA_SINCRONIZACION.Cron, func() {
|
||||
// log.Println("Iniciando ejecución programada de servicios")
|
||||
// execCtx, execCancel := context.WithCancel(ctx)
|
||||
// defer execCancel()
|
||||
//
|
||||
// errors := services.RunAllServices(execCtx, servicesList, true)
|
||||
// if len(errors) > 0 {
|
||||
// errMsg := "Errores durante la ejecución programada:"
|
||||
// for _, err := range errors {
|
||||
// errMsg += "\n- " + err.Error()
|
||||
// }
|
||||
// if notifier != nil {
|
||||
// notifier.SendNotification("Errores en ejecución programada", errMsg)
|
||||
// }
|
||||
// log.Println(errMsg)
|
||||
// } else {
|
||||
// log.Println("Ejecución programada completada exitosamente")
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// if err != nil {
|
||||
// log.Fatalf("Error al programar tareas: %v", err)
|
||||
// }
|
||||
//
|
||||
// // Configurar segundo cron
|
||||
// cronGetCode := cron.New(cron.WithSeconds())
|
||||
// _, err = cronGetCode.AddFunc(cfg.Schedule.FACTURA_CODIGO.Cron, func() {
|
||||
// log.Println("Iniciando ejecución programada para Obtener Codigos")
|
||||
// execCtx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel()
|
||||
// //execCtx, execCancel := context.WithCancel(ctx)
|
||||
// //defer execCancel()
|
||||
//
|
||||
// errors := services.RunAllGetCodeServices(execCtx, servicesList2, true)
|
||||
// if len(errors) > 0 {
|
||||
// errMsg := "Errores durante la ejecución programada Obtener Codigos:"
|
||||
// for _, err := range errors {
|
||||
// errMsg += "\n- " + err.Error()
|
||||
// }
|
||||
// if notifier != nil {
|
||||
// notifier.SendNotification("Errores en ejecución programada Obtener Codigos", errMsg)
|
||||
// }
|
||||
// log.Println(errMsg)
|
||||
// } else {
|
||||
// log.Println("Ejecución programada completada exitosamente Obtener Codigos")
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// if err != nil {
|
||||
// log.Fatalf("Error al programar tareas Obtener Codigos: %v", err)
|
||||
// }
|
||||
//
|
||||
// // Iniciar ambos cron
|
||||
// c.Start()
|
||||
// cronGetCode.Start()
|
||||
//
|
||||
// log.Printf("Demonio de servicios iniciado. Programación: %s", cfg.Schedule.FACTURA_SINCRONIZACION.Cron)
|
||||
// log.Printf("Demonio de obtención de códigos iniciado. Programación: %s", cfg.Schedule.FACTURA_CODIGO.Cron)
|
||||
//
|
||||
// if notifier != nil {
|
||||
// notifier.SendNotification("Servicios iniciados",
|
||||
// fmt.Sprintf("Los servicios SOAP han sido iniciados con programación: %s", cfg.Schedule.FACTURA_SINCRONIZACION.Cron))
|
||||
// }
|
||||
//
|
||||
// // Esperar a que el contexto termine y luego detener ambos cron
|
||||
// <-ctx.Done()
|
||||
// c.Stop()
|
||||
// cronGetCode.Stop()
|
||||
// log.Println("Demonios detenidos correctamente")
|
||||
//}
|
||||
|
||||
//package obtencionCodigo
|
||||
|
||||
//
|
||||
//import (
|
||||
// "context"
|
||||
// "daemonService/internal/services/procesar"
|
||||
// "database/sql"
|
||||
// "encoding/json"
|
||||
// "errors"
|
||||
// "fmt"
|
||||
// "log"
|
||||
// "net/http"
|
||||
// "strconv"
|
||||
// "time"
|
||||
//
|
||||
// "daemonService/internal/models/obtencionCodigos"
|
||||
// "daemonService/internal/models/obtencionCodigos/request"
|
||||
// "daemonService/internal/models/obtencionCodigos/response"
|
||||
//)
|
||||
//
|
||||
//// Constantes para la configuración general
|
||||
//const (
|
||||
// maxBodySize = 1 * 1024 * 1024 // 1MB
|
||||
// defaultTimeZone = "America/La_Paz"
|
||||
// logServiceStart = "Iniciando servicio %s"
|
||||
// logServiceEnd = "Finalizado servicio %s"
|
||||
// logErrorProcessing = "Error procesando registro %s: %v"
|
||||
//)
|
||||
//
|
||||
//// Errores comunes
|
||||
//var (
|
||||
// ErrAllFieldsRequired = errors.New("todos los campos son requeridos")
|
||||
// ErrUnexpectedRespType = errors.New("tipo de respuesta inesperado")
|
||||
//)
|
||||
//
|
||||
//// mapea toda la tabla registroEmpresa
|
||||
//type RegistroEmpresa struct {
|
||||
// ID int `sql:"id"`
|
||||
// CodigoAmbiente int `sql:"codigo_ambiente"`
|
||||
// CodigoModalidad int `sql:"codigo_modalidad"`
|
||||
// CodigoPuntoVenta int `sql:"codigo_punto_venta"`
|
||||
// CodigoSistema string `sql:"codigo_sistema"`
|
||||
// CodigoSucursal int `sql:"codigo_sucursal"`
|
||||
// Nit string `sql:"nit"`
|
||||
// FechaCreacion time.Time `sql:"fecha_creacion"`
|
||||
// FechaActualizacion time.Time `sql:"fecha_actualizacion"`
|
||||
//}
|
||||
//
|
||||
//// solo tiene los dos campos que quieres de la tabla cuis
|
||||
//type CuisMinimal struct {
|
||||
// Cuis_id int64 `sql:"id"`
|
||||
// Cuis string `sql:"cuis"`
|
||||
// FechaVigencia time.Time `sql:"fecha_vigencia"`
|
||||
//}
|
||||
//
|
||||
//// EmpresaConCuis agrupa una empresa con sus cuis mínimos
|
||||
//type EmpresaConCuis struct {
|
||||
// RegistroEmpresa
|
||||
// Cuis []CuisMinimal
|
||||
//}
|
||||
//
|
||||
//type ObtencionCodigoService struct {
|
||||
// obtencionCodigos.CuisServiceModel
|
||||
// procesar.Procesar
|
||||
// Logger *log.Logger
|
||||
//}
|
||||
//
|
||||
//// retorna el nombre del servicio
|
||||
//func (s *ObtencionCodigoService) GetName() string {
|
||||
// return s.Name
|
||||
//}
|
||||
//
|
||||
//// registra un mensaje con el prefijo del servicio
|
||||
//func (s *ObtencionCodigoService) logMessage(format string, v ...interface{}) {
|
||||
// if s.Logger != nil {
|
||||
// s.Logger.Printf(format, v...)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// registra un error y devuelve el mismo error
|
||||
//func (s *ObtencionCodigoService) logError(format string, err error, v ...interface{}) error {
|
||||
// args := append([]interface{}{err}, v...)
|
||||
// if s.Logger != nil {
|
||||
// s.Logger.Printf(format, args...)
|
||||
// }
|
||||
// return fmt.Errorf(format, args...)
|
||||
//}
|
||||
//
|
||||
//// maneja la obtención de códigos CUIS
|
||||
//func (s *ObtencionCodigoService) ExecuteGetCode(dbCtx context.Context, w http.ResponseWriter, req *http.Request, isCron bool) (interface{}, error) {
|
||||
// s.logMessage(logServiceStart, s.Name)
|
||||
// defer s.logMessage(logServiceEnd, s.Name)
|
||||
//
|
||||
// var respCuis interface{}
|
||||
// var err error
|
||||
//
|
||||
// if isCron {
|
||||
// respCuis, err = s.processCronMode(dbCtx)
|
||||
// } else {
|
||||
// respCuis, err = s.processAPIMode(dbCtx, w, req)
|
||||
// }
|
||||
//
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return respCuis, nil
|
||||
//}
|
||||
//
|
||||
//// procesa la ejecución en modo cron
|
||||
//func (s *ObtencionCodigoService) processCronMode(dbCtx context.Context) (interface{}, error) {
|
||||
// empresasConCuis, err := s.GetEmpresasConCuisMinimal(dbCtx)
|
||||
// if err != nil {
|
||||
// return nil, s.logError("Error al obtener empresas con CUIS: %v", err)
|
||||
// }
|
||||
//
|
||||
// var lastResponse interface{}
|
||||
// for _, ec := range empresasConCuis {
|
||||
// s.logMessage("Procesando empresa ID: %d", ec.ID)
|
||||
//
|
||||
// if len(ec.Cuis) == 0 {
|
||||
// s.logMessage("Empresa ID %d sin CUIS registrados", ec.ID)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// for _, c := range ec.Cuis {
|
||||
// s.logMessage("Procesando CUIS ID: %d", c.Cuis_id)
|
||||
//
|
||||
// cuisRequest := s.buildCuisRequestFromEmpresa(ec)
|
||||
// respCuis, err := s.CodigoProcesarRegistro(&s.CuisServiceModel, cuisRequest, 0)
|
||||
//
|
||||
// if err != nil {
|
||||
// s.logError(logErrorProcessing, err, cuisRequest.CodigoSistema)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// if err := s.actualizarRegistro(dbCtx, ec.ID, c.Cuis_id, cuisRequest, respCuis); err != nil {
|
||||
// s.logError("Error actualizando registro: %v", err)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// lastResponse = respCuis
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return lastResponse, nil
|
||||
//}
|
||||
//
|
||||
//// procesa la ejecución en modo API
|
||||
//func (s *ObtencionCodigoService) processAPIMode(dbCtx context.Context, w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
// cuisRequest, err := s.parseCuisRequest(w, req)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if err := s.validateCuisRequest(cuisRequest); err != nil {
|
||||
// return nil, s.logError("Datos inválidos: %v", err)
|
||||
// }
|
||||
//
|
||||
// respCuis, err := s.CodigoProcesarRegistro(&s.CuisServiceModel, cuisRequest, 0)
|
||||
// if err != nil {
|
||||
// return nil, s.logError(logErrorProcessing, err, cuisRequest.CodigoSistema)
|
||||
// }
|
||||
//
|
||||
// if err := s.crearNuevoRegistro(dbCtx, cuisRequest, respCuis); err != nil {
|
||||
// return nil, s.logError("Error creando nuevo registro: %v", err)
|
||||
// }
|
||||
//
|
||||
// return respCuis, nil
|
||||
//}
|
||||
//
|
||||
//// parsea la solicitud de CUIS desde el body del request
|
||||
//func (s *ObtencionCodigoService) parseCuisRequest(w http.ResponseWriter, req *http.Request) (request.SolicitudCuis, error) {
|
||||
// var cuisRequest request.SolicitudCuis
|
||||
//
|
||||
// // Limitar el tamaño del body para prevenir ataques DoS
|
||||
// req.Body = http.MaxBytesReader(w, req.Body, maxBodySize)
|
||||
//
|
||||
// decoder := json.NewDecoder(req.Body)
|
||||
// decoder.DisallowUnknownFields() // Rechazar campos desconocidos
|
||||
//
|
||||
// if err := decoder.Decode(&cuisRequest); err != nil {
|
||||
// return cuisRequest, s.logError("Error al decodificar JSON: %v", err)
|
||||
// }
|
||||
//
|
||||
// return cuisRequest, nil
|
||||
//}
|
||||
//
|
||||
//// valida los datos de la solicitud CUIS
|
||||
//func (s *ObtencionCodigoService) validateCuisRequest(request request.SolicitudCuis) error {
|
||||
// if request.CodigoAmbiente == "" ||
|
||||
// request.CodigoModalidad == "" ||
|
||||
// request.CodigoPuntoVenta == "" ||
|
||||
// request.CodigoSistema == "" ||
|
||||
// request.CodigoSucursal == "" ||
|
||||
// request.Nit == "" {
|
||||
// return ErrAllFieldsRequired
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//// crea una solicitud CUIS a partir de datos de empresa
|
||||
//func (s *ObtencionCodigoService) buildCuisRequestFromEmpresa(ec EmpresaConCuis) request.SolicitudCuis {
|
||||
// return request.SolicitudCuis{
|
||||
// CodigoAmbiente: strconv.Itoa(ec.CodigoAmbiente),
|
||||
// CodigoModalidad: strconv.Itoa(ec.CodigoModalidad),
|
||||
// CodigoPuntoVenta: strconv.Itoa(ec.CodigoPuntoVenta),
|
||||
// CodigoSistema: ec.CodigoSistema,
|
||||
// CodigoSucursal: strconv.Itoa(ec.CodigoSucursal),
|
||||
// Nit: ec.Nit,
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// obtiene todas las empresas con sus CUIS
|
||||
//func (s *ObtencionCodigoService) GetEmpresasConCuisMinimal(ctx context.Context) ([]EmpresaConCuis, error) {
|
||||
// const query = `
|
||||
// SELECT
|
||||
// -- columnas de registroEmpresa
|
||||
// re.id, re.codigo_ambiente, re.codigo_modalidad, re.codigo_punto_venta,
|
||||
// re.codigo_sistema, re.codigo_sucursal, re.nit,
|
||||
// re.fecha_creacion, re.fecha_actualizacion,
|
||||
// -- columnas específicas de cuis
|
||||
// c.id, c.cuis, c.fecha_vigencia
|
||||
// FROM registroEmpresa re
|
||||
// LEFT JOIN cuis c ON c.registro_empresa_id = re.id
|
||||
// ORDER BY re.id;
|
||||
// `
|
||||
//
|
||||
// rows, err := s.DB.QueryContext(ctx, query)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("consulta empresas con cuis minimal: %w", err)
|
||||
// }
|
||||
// defer rows.Close()
|
||||
//
|
||||
// // Mapa temporal para agrupar por empresa.ID
|
||||
// empresasMap := make(map[int]*EmpresaConCuis)
|
||||
//
|
||||
// for rows.Next() {
|
||||
// // Variables temporales de escaneo
|
||||
// var (
|
||||
// id int
|
||||
// codigoAmbiente int
|
||||
// codigoModalidad int
|
||||
// codigoPuntoVenta int
|
||||
// codigoSistema string
|
||||
// codigoSucursal int
|
||||
// nit string
|
||||
// fechaCreacion time.Time
|
||||
// fechaActualizacion time.Time
|
||||
//
|
||||
// idCuis sql.NullInt64
|
||||
// cuis sql.NullString
|
||||
// fechaVigencia sql.NullTime
|
||||
// )
|
||||
//
|
||||
// // Leer la fila
|
||||
// if err := rows.Scan(
|
||||
// &id, &codigoAmbiente, &codigoModalidad, &codigoPuntoVenta,
|
||||
// &codigoSistema, &codigoSucursal, &nit,
|
||||
// &fechaCreacion, &fechaActualizacion,
|
||||
// &idCuis, &cuis, &fechaVigencia,
|
||||
// ); err != nil {
|
||||
// return nil, fmt.Errorf("scan fila: %w", err)
|
||||
// }
|
||||
//
|
||||
// // ¿Ya existe la empresa en el mapa?
|
||||
// ent, ok := empresasMap[id]
|
||||
// if !ok {
|
||||
// // Si no existe, la creamos y rellenamos datos de registroEmpresa
|
||||
// ent = &EmpresaConCuis{
|
||||
// RegistroEmpresa: RegistroEmpresa{
|
||||
// ID: id,
|
||||
// CodigoAmbiente: codigoAmbiente,
|
||||
// CodigoModalidad: codigoModalidad,
|
||||
// CodigoPuntoVenta: codigoPuntoVenta,
|
||||
// CodigoSistema: codigoSistema,
|
||||
// CodigoSucursal: codigoSucursal,
|
||||
// Nit: nit,
|
||||
// FechaCreacion: fechaCreacion,
|
||||
// FechaActualizacion: fechaActualizacion,
|
||||
// },
|
||||
// Cuis: make([]CuisMinimal, 0, 1),
|
||||
// }
|
||||
// empresasMap[id] = ent
|
||||
// }
|
||||
//
|
||||
// // Si hay un CUIS válido, lo añadimos al slice
|
||||
// if idCuis.Valid && cuis.Valid && fechaVigencia.Valid {
|
||||
// ent.Cuis = append(ent.Cuis, CuisMinimal{
|
||||
// Cuis_id: idCuis.Int64,
|
||||
// Cuis: cuis.String,
|
||||
// FechaVigencia: fechaVigencia.Time,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if err := rows.Err(); err != nil {
|
||||
// return nil, fmt.Errorf("iteración filas: %w", err)
|
||||
// }
|
||||
//
|
||||
// // Convertimos el mapa a slice ordenado por aparición
|
||||
// resp := make([]EmpresaConCuis, 0, len(empresasMap))
|
||||
// for _, ent := range empresasMap {
|
||||
// resp = append(resp, *ent)
|
||||
// }
|
||||
// return resp, nil
|
||||
//}
|
||||
//
|
||||
//// busca un registro de empresa por sus criterios
|
||||
//func (s *ObtencionCodigoService) buscarRegistroEmpresa(ctx context.Context, request request.SolicitudCuis) (int, error) {
|
||||
// query := `
|
||||
// SELECT id
|
||||
// FROM registroEmpresa
|
||||
// WHERE codigo_sistema = $1
|
||||
// AND nit = $2
|
||||
// AND codigo_ambiente = $3
|
||||
// AND codigo_punto_venta = $4
|
||||
// AND codigo_sucursal = $5
|
||||
// LIMIT 1
|
||||
// `
|
||||
//
|
||||
// var id int
|
||||
// err := s.DB.QueryRowContext(
|
||||
// ctx,
|
||||
// query,
|
||||
// request.CodigoSistema,
|
||||
// request.Nit,
|
||||
// request.CodigoAmbiente,
|
||||
// request.CodigoPuntoVenta,
|
||||
// request.CodigoSucursal,
|
||||
// ).Scan(&id)
|
||||
//
|
||||
// if err == sql.ErrNoRows {
|
||||
// return 0, nil // No existe el registro
|
||||
// }
|
||||
//
|
||||
// if err != nil {
|
||||
// return 0, err
|
||||
// }
|
||||
//
|
||||
// return id, nil
|
||||
//}
|
||||
//
|
||||
//// obtiene la hora actual en la zona horaria de Bolivia
|
||||
//func (s *ObtencionCodigoService) getCurrentBoliviaTime() (time.Time, error) {
|
||||
// loc, err := time.LoadLocation(defaultTimeZone)
|
||||
// if err != nil {
|
||||
// return time.Time{}, err
|
||||
// }
|
||||
// return time.Now().In(loc), nil
|
||||
//}
|
||||
//
|
||||
//// parsea la respuesta SOAP para CUIS
|
||||
//func (s *ObtencionCodigoService) parseSoapCuisResponse(respSoap interface{}) (string, string, bool, error) {
|
||||
// resp, ok := respSoap.(response.SoapBodyCuis)
|
||||
// if !ok {
|
||||
// return "", "", false, fmt.Errorf("%w: %T", ErrUnexpectedRespType, respSoap)
|
||||
// }
|
||||
//
|
||||
// return resp.Response.Respuesta.Codigo,
|
||||
// resp.Response.Respuesta.FechaVigencia,
|
||||
// resp.Response.Respuesta.Transaccion,
|
||||
// nil
|
||||
//}
|
||||
//
|
||||
//// crea un nuevo registro de empresa y CUIS
|
||||
//func (s *ObtencionCodigoService) crearNuevoRegistro(ctx context.Context, request request.SolicitudCuis, respSoap interface{}) error {
|
||||
// currentBolivia, err := s.getCurrentBoliviaTime()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // Parsear respuesta
|
||||
// codigoCuis, fechaVigencia, transaccion, err := s.parseSoapCuisResponse(respSoap)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // Iniciar transacción
|
||||
// tx, err := s.DB.BeginTx(ctx, nil)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al iniciar transacción: %w", err)
|
||||
// }
|
||||
// defer tx.Rollback()
|
||||
//
|
||||
// // Verificar si ya existe el NIT en la base de datos
|
||||
// var existeNIT bool
|
||||
// queryVerificacionNIT := `
|
||||
// SELECT EXISTS (
|
||||
// SELECT 1 FROM registroEmpresa
|
||||
// WHERE nit = $1
|
||||
// )
|
||||
// `
|
||||
// err = tx.QueryRowContext(
|
||||
// ctx,
|
||||
// queryVerificacionNIT,
|
||||
// request.Nit,
|
||||
// ).Scan(&existeNIT)
|
||||
//
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al verificar existencia del NIT: %w", err)
|
||||
// }
|
||||
//
|
||||
// // Si el NIT ya existe, verificar si la combinación de sucursal y punto de venta existe
|
||||
// if existeNIT {
|
||||
// var existeCombinacion bool
|
||||
// queryVerificacionCombinacion := `
|
||||
// SELECT EXISTS (
|
||||
// SELECT 1 FROM registroEmpresa
|
||||
// WHERE nit = $1
|
||||
// AND codigo_sucursal = $2
|
||||
// AND codigo_punto_venta = $3
|
||||
// )
|
||||
// `
|
||||
// err = tx.QueryRowContext(
|
||||
// ctx,
|
||||
// queryVerificacionCombinacion,
|
||||
// request.Nit,
|
||||
// request.CodigoSucursal,
|
||||
// request.CodigoPuntoVenta,
|
||||
// ).Scan(&existeCombinacion)
|
||||
//
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al verificar combinación de sucursal y punto de venta: %w", err)
|
||||
// }
|
||||
//
|
||||
// if existeCombinacion {
|
||||
// return fmt.Errorf("ya existe un registro con el mismo NIT, código sucursal y código punto de venta")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Insertar en registroEmpresa
|
||||
// query := `
|
||||
// INSERT INTO registroEmpresa (
|
||||
// codigo_ambiente,
|
||||
// codigo_modalidad,
|
||||
// codigo_punto_venta,
|
||||
// codigo_sistema,
|
||||
// codigo_sucursal,
|
||||
// nit
|
||||
// ) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
// RETURNING id
|
||||
// `
|
||||
// var registroID int
|
||||
// err = tx.QueryRowContext(
|
||||
// ctx,
|
||||
// query,
|
||||
// request.CodigoAmbiente,
|
||||
// request.CodigoModalidad,
|
||||
// request.CodigoPuntoVenta,
|
||||
// request.CodigoSistema,
|
||||
// request.CodigoSucursal,
|
||||
// request.Nit,
|
||||
// ).Scan(®istroID)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al insertar registro: %w", err)
|
||||
// }
|
||||
//
|
||||
// // Insertar en cuis
|
||||
// queryInsertCuis := `
|
||||
// INSERT INTO cuis (cuis, fecha_vigencia, transaccion, fecha_creacion, registro_empresa_id)
|
||||
// VALUES ($1, $2, $3, $4, $5)
|
||||
// `
|
||||
// _, err = tx.ExecContext(ctx, queryInsertCuis, codigoCuis, fechaVigencia, transaccion, currentBolivia, registroID)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error al insertar CUIS: %w", err)
|
||||
// }
|
||||
//
|
||||
// if err = tx.Commit(); err != nil {
|
||||
// return fmt.Errorf("error al confirmar transacción: %w", err)
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//// actualiza un registro existente de empresa y CUIS
|
||||
//func (s *ObtencionCodigoService) actualizarRegistro(ctx context.Context, empresaID int, cuisID int64, req request.SolicitudCuis, respRaw interface{}) error {
|
||||
// currentBolivia, err := s.getCurrentBoliviaTime()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // 1. Iniciar tx
|
||||
// tx, err := s.DB.BeginTx(ctx, nil)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("iniciar transacción: %w", err)
|
||||
// }
|
||||
// defer tx.Rollback()
|
||||
//
|
||||
// // 2. Parsear respuesta
|
||||
// nuevoCuis, fechaVigencia, _, err := s.parseSoapCuisResponse(respRaw)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // 3. Actualizar empresa
|
||||
// _, err = tx.ExecContext(ctx, `
|
||||
// UPDATE registroEmpresa
|
||||
// SET codigo_ambiente=$1, codigo_modalidad=$2, codigo_punto_venta=$3,
|
||||
// codigo_sistema=$4, codigo_sucursal=$5, nit=$6, fecha_actualizacion=$7
|
||||
// WHERE id=$8
|
||||
// `,
|
||||
// req.CodigoAmbiente, req.CodigoModalidad, req.CodigoPuntoVenta,
|
||||
// req.CodigoSistema, req.CodigoSucursal, req.Nit, currentBolivia, empresaID,
|
||||
// )
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("actualizar registroEmpresa: %w", err)
|
||||
// }
|
||||
//
|
||||
// // 4. Comprobar si hay un CUIS existente
|
||||
// var existingCuis string
|
||||
// err = tx.QueryRowContext(ctx, `SELECT cuis FROM cuis WHERE id=$1 AND registro_empresa_id=$2`, cuisID, empresaID).Scan(&existingCuis)
|
||||
// if err != nil && err != sql.ErrNoRows {
|
||||
// return fmt.Errorf("consultar cuis existente: %w", err)
|
||||
// }
|
||||
//
|
||||
// // 5. Insertar o actualizar
|
||||
// if err == sql.ErrNoRows {
|
||||
// _, err = tx.ExecContext(ctx, `INSERT INTO cuis (registro_empresa_id, cuis) VALUES ($1, $2)`, empresaID, nuevoCuis)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("insertar nuevo cuis: %w", err)
|
||||
// }
|
||||
// } else if existingCuis != nuevoCuis {
|
||||
// _, err = tx.ExecContext(ctx, `UPDATE cuis SET cuis=$1, fecha_vigencia=$2, fecha_actualizacion=$3 WHERE id=$4`,
|
||||
// nuevoCuis, fechaVigencia, currentBolivia, cuisID)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("actualizar cuis existente: %w", err)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 6. Confirmar tx
|
||||
// if err := tx.Commit(); err != nil {
|
||||
// return fmt.Errorf("confirmar transacción: %w", err)
|
||||
// }
|
||||
// return nil
|
||||
//}
|
@ -0,0 +1,107 @@
|
||||
package procesar
|
||||
|
||||
import (
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
oc "daemonService/internal/models/obtencionCodigos/request"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func codigoGenerarSOAPRequest(s obtencionCodigos.CuisServiceModel, solicitud oc.SolicitudCuisCufd) (string, error) {
|
||||
|
||||
var xmlBytes []byte
|
||||
var err error
|
||||
var envelope oc.SOAPRequestEnvelope
|
||||
var envelopeCufd oc.SOAPRequestEnvelopeCufd
|
||||
switch solicitud.Cuis {
|
||||
case "":
|
||||
envelope = oc.SOAPRequestEnvelope{
|
||||
XmlnsSoapenv: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
XmlnsSiat: "https://siat.impuestos.gob.bo/",
|
||||
Header: &oc.SOAPRequestHeader{},
|
||||
Body: oc.SOAPRequestBody{
|
||||
Operacion: oc.OperacionXML{
|
||||
XMLName: xml.Name{Local: "siat:" + s.TagNames},
|
||||
Solicitud: oc.SolicitudCuisCufd{
|
||||
CodigoAmbiente: solicitud.CodigoAmbiente,
|
||||
CodigoModalidad: solicitud.CodigoModalidad,
|
||||
CodigoPuntoVenta: solicitud.CodigoPuntoVenta,
|
||||
CodigoSistema: solicitud.CodigoSistema,
|
||||
CodigoSucursal: solicitud.CodigoSucursal,
|
||||
Nit: solicitud.Nit,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
// Se genera el XML con indentación para mayor legibilidad
|
||||
xmlBytes, err = xml.MarshalIndent(envelope, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error al generar XML: %w", err)
|
||||
}
|
||||
break
|
||||
default:
|
||||
envelopeCufd = oc.SOAPRequestEnvelopeCufd{
|
||||
XmlnsSoapenv: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
XmlnsSiat: "https://siat.impuestos.gob.bo/",
|
||||
Header: &oc.SOAPRequestHeaderCufd{},
|
||||
Body: oc.SOAPRequestBodyCufd{
|
||||
Operacion: oc.OperacionXMLCufd{
|
||||
XMLName: xml.Name{Local: "siat:" + s.TagNamesCufd},
|
||||
Solicitud: oc.SolicitudCuisCufd{
|
||||
CodigoAmbiente: solicitud.CodigoAmbiente,
|
||||
CodigoModalidad: solicitud.CodigoModalidad,
|
||||
CodigoPuntoVenta: solicitud.CodigoPuntoVenta,
|
||||
CodigoSistema: solicitud.CodigoSistema,
|
||||
CodigoSucursal: solicitud.CodigoSucursal,
|
||||
Cuis: solicitud.Cuis,
|
||||
Nit: solicitud.Nit,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
// Se genera el XML con indentación para mayor legibilidad
|
||||
xmlBytes, err = xml.MarshalIndent(envelopeCufd, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error al generar XML: %w", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Se concatena la cabecera XML
|
||||
soapRequest := xml.Header + string(xmlBytes)
|
||||
|
||||
return soapRequest, nil
|
||||
}
|
||||
|
||||
//func codigoGenerarSOAPRequest(s obtencionCodigos.CuisServiceModel, solicitud oc.SolicitudCuisCufd) (string, error) {
|
||||
//
|
||||
// envelope := oc.SOAPRequestEnvelope{
|
||||
// XmlnsSoapenv: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
// XmlnsSiat: "https://siat.impuestos.gob.bo/",
|
||||
// Header: &oc.SOAPRequestHeader{},
|
||||
// Body: oc.SOAPRequestBody{
|
||||
// Operacion: oc.OperacionXML{
|
||||
// XMLName: xml.Name{Local: "siat:" + s.TagNames},
|
||||
// Solicitud: oc.SolicitudCuisCufd{
|
||||
// CodigoAmbiente: solicitud.CodigoAmbiente,
|
||||
// CodigoModalidad: solicitud.CodigoModalidad,
|
||||
// CodigoPuntoVenta: solicitud.CodigoPuntoVenta,
|
||||
// CodigoSistema: solicitud.CodigoSistema,
|
||||
// CodigoSucursal: solicitud.CodigoSucursal,
|
||||
// Nit: solicitud.Nit,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// // Se genera el XML con indentación para mayor legibilidad
|
||||
// xmlBytes, err := xml.MarshalIndent(envelope, "", " ")
|
||||
// if err != nil {
|
||||
// return "", fmt.Errorf("error al generar XML: %w", err)
|
||||
// }
|
||||
//
|
||||
// // Se concatena la cabecera XML
|
||||
// soapRequest := xml.Header + string(xmlBytes)
|
||||
//
|
||||
// return soapRequest, nil
|
||||
//}
|
@ -0,0 +1,60 @@
|
||||
package procesar
|
||||
|
||||
//
|
||||
//import (
|
||||
// "daemonService/internal/models/obtencionCodigos"
|
||||
// "daemonService/internal/models/obtencionCodigos/request"
|
||||
// "daemonService/internal/models/obtencionCodigos/response"
|
||||
// oc "daemonService/internal/soap/obtencionCodigo"
|
||||
// "fmt"
|
||||
// "log"
|
||||
// "net/http"
|
||||
//)
|
||||
//
|
||||
//type ProcesarRegistroCufd struct {
|
||||
// CuisServiceModel obtencionCodigos.CuisServiceModel
|
||||
//}
|
||||
//
|
||||
//// instaciamos procesarRegistro
|
||||
//func NewProcesarRegistroCufd(
|
||||
// cuisServiceModel obtencionCodigos.CuisServiceModel,
|
||||
//) *ProcesarRegistroCufd {
|
||||
// return &ProcesarRegistroCufd{CuisServiceModel: cuisServiceModel}
|
||||
//}
|
||||
//
|
||||
//func (s *ProcesarRegistroCufd) CodigoProcesarRegistroCufd(solicitud request.SolicitudCuisCufd, cuisID int) (interface{}, error) {
|
||||
// //func codigoProcesarRegistro(s *obtencionCodigos.CuisServiceModel, solicitud request.SolicitudCuis, cuisID int) (interface{}, error) {
|
||||
//
|
||||
// log.Printf("Procesando código sistema: %s", solicitud.CodigoSistema)
|
||||
//
|
||||
// // 1. Generar solicitud SOAP
|
||||
// soapReq, err := codigoGenerarSOAPRequest(s.CuisServiceModel, solicitud)
|
||||
// if err != nil {
|
||||
// //return fmt.Errorf("error generando SOAP: %v", err)
|
||||
// fmt.Printf("error generando SOAP: %v", err)
|
||||
// }
|
||||
//
|
||||
// // 2. Enviar solicitud con reintentos CUIS
|
||||
// //resp, bodyBytes, err := s.enviarSOAPRequest(soapReq)
|
||||
// resp, bodyBytes, err := oc.CodigoEnviarSOAPRequest(s.CuisServiceModel, soapReq)
|
||||
// if err != nil {
|
||||
// fmt.Printf("error enviando SOAP: %v", err)
|
||||
// return nil, fmt.Errorf("error enviando SOAP: %v", err)
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
//
|
||||
// // 3. Validar respuesta HTTP
|
||||
// if resp.StatusCode != http.StatusOK {
|
||||
// //return fmt.Errorf("código de estado HTTP inválido: %d", resp.StatusCode)
|
||||
// fmt.Printf("código de estado HTTP inválido: %d", resp.StatusCode)
|
||||
// }
|
||||
//
|
||||
// // 4. Parsear respuesta
|
||||
// //println("respuestaCUIS:", string(bodyBytes))
|
||||
// respCuis, err := oc.CuisParsearRespSOAP[[]response.CuisResponse](&s.CuisServiceModel, bodyBytes)
|
||||
// if err != nil {
|
||||
// //return fmt.Errorf("error parseando respuesta: %v", err)
|
||||
// fmt.Printf("error parseando respuesta: %v", err)
|
||||
// }
|
||||
// return respCuis, nil
|
||||
//}
|
@ -0,0 +1,102 @@
|
||||
package procesar
|
||||
|
||||
import (
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
"daemonService/internal/models/obtencionCodigos/request"
|
||||
"daemonService/internal/models/obtencionCodigos/response"
|
||||
oc "daemonService/internal/soap/obtencionCodigo"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ProcesarRegistro struct {
|
||||
CuisServiceModel obtencionCodigos.CuisServiceModel
|
||||
}
|
||||
|
||||
// instaciamos procesarRegistro
|
||||
func NewProcesarRegistro(
|
||||
cuisServiceModel obtencionCodigos.CuisServiceModel,
|
||||
) *ProcesarRegistro {
|
||||
return &ProcesarRegistro{CuisServiceModel: cuisServiceModel}
|
||||
}
|
||||
|
||||
func (s *ProcesarRegistro) CodigoProcesarRegistro(solicitud request.SolicitudCuisCufd) (interface{}, interface{}, error) {
|
||||
|
||||
log.Printf("Procesando código sistema: %s", solicitud.CodigoSistema)
|
||||
|
||||
// cargamos las variables apikey y su token
|
||||
s.CuisServiceModel.TokenKey = solicitud.KeyToken
|
||||
s.CuisServiceModel.TokenValue = solicitud.ValueToken
|
||||
|
||||
// 1. Generar solicitud SOAP
|
||||
soapReq, err := codigoGenerarSOAPRequest(s.CuisServiceModel, solicitud)
|
||||
if err != nil {
|
||||
fmt.Printf("error generando SOAP: %v", err)
|
||||
return nil, nil, fmt.Errorf("error generando SOAP: %v", err)
|
||||
}
|
||||
|
||||
// 2. Enviar solicitud con reintentos CUIS
|
||||
resp, bodyBytes, err := oc.CodigoEnviarSOAPRequest(s.CuisServiceModel, soapReq)
|
||||
if err != nil {
|
||||
fmt.Printf("error enviando SOAP: %v", err)
|
||||
return nil, nil, fmt.Errorf("error enviando SOAP: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 3. Validar respuesta HTTP
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Printf("código de estado HTTP inválido: %d", resp.StatusCode)
|
||||
return nil, nil, fmt.Errorf("código de estado HTTP inválido: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// 4. Parsear respuesta
|
||||
var respCuis response.SoapBodyCuis
|
||||
var respCufd response.SoapBodyCufd
|
||||
|
||||
switch solicitud.Cuis {
|
||||
case "":
|
||||
respCuis, err = oc.CuisParsearRespSOAP[[]response.CuisResponse](&s.CuisServiceModel, bodyBytes)
|
||||
if err != nil {
|
||||
fmt.Printf("error parseando respuesta: %v", err)
|
||||
return nil, nil, fmt.Errorf("error parseando respuesta: %v", err)
|
||||
}
|
||||
break
|
||||
default:
|
||||
respCufd, err = oc.CufdParsearRespSOAP[[]response.CufdResponse](&s.CuisServiceModel, bodyBytes)
|
||||
if err != nil {
|
||||
fmt.Printf("error parseando respuesta: %v", err)
|
||||
return nil, nil, fmt.Errorf("error parseando respuesta: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 4. Parsear respuesta
|
||||
//var leyendas any
|
||||
//switch s.TagNames {
|
||||
//case "sincronizarListaLeyendasFactura":
|
||||
// leyendas, err = soap.ParsearRespLeyendaFacturaSOAP[[]response.LeyendaDetalle](s, bodyBytes)
|
||||
// //leyendas, err := soap.ParsearRespLeyendaFacturaSOAP(s, bodyBytes)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error parseando respuesta: %v", err)
|
||||
// }
|
||||
// break
|
||||
//case "sincronizarParametricaTipoDocumentoIdentidad":
|
||||
// leyendas, err = soap.ParsearRespTipoDocumentoSOAP[[]response.CodigoDetalle](s, bodyBytes)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error parseando respuesta: %v", err)
|
||||
// }
|
||||
// break
|
||||
//default:
|
||||
// log.Println("no ingreso por ninguna de las opciones")
|
||||
//}
|
||||
|
||||
// 5. Guardar en base de datos
|
||||
//if err := s.guardarRegistro(solicitud, cuisID, soapReq, bodyBytes, leyendas); err != nil {
|
||||
//if err := repositories.GuardarRegistro(s, solicitud, cuisID, soapReq, bodyBytes, leyendas); err != nil {
|
||||
// return fmt.Errorf("error guardando registro: %v", err)
|
||||
//}
|
||||
|
||||
//return nil
|
||||
return respCuis, respCufd, nil
|
||||
}
|
59
daemonService/internal/services/runAllServices.go
Normal file
59
daemonService/internal/services/runAllServices.go
Normal file
@ -0,0 +1,59 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/models"
|
||||
"daemonService/internal/models/obtencionCodigos"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func RunAllServices(ctx context.Context, services []models.SoapService, isCron bool) []error {
|
||||
var errors []error
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
|
||||
for _, service := range services {
|
||||
s := service
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
serviceCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
_, err := s.Execute(serviceCtx, nil, isCron)
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
errors = append(errors, err)
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
log.Println("Todos los servicios han sido ejecutados")
|
||||
return errors
|
||||
}
|
||||
|
||||
func RunAllGetCodeServices(ctx context.Context, services []obtencionCodigos.CuisService, isCron bool) []error {
|
||||
var errors []error
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
|
||||
for _, service := range services {
|
||||
s := service
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
serviceCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
_, err := s.ExecuteGetCode(serviceCtx, nil, nil, isCron)
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
errors = append(errors, err)
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
log.Println("Todos los servicios han sido ejecutados Obtener Codigos")
|
||||
return errors
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package sincronizacionDatos
|
||||
|
||||
import (
|
||||
"daemonService/internal/models"
|
||||
sd "daemonService/internal/models/sincronizacionDatos/request"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func GenerarSOAPRequest(s *models.ServiceModel, solicitud sd.SolicitudSincronizacion) (string, error) {
|
||||
// Validar datos de entrada
|
||||
//if err := validarSolicitud(solicitud); err != nil {
|
||||
// return "", err
|
||||
//}
|
||||
|
||||
envelope := sd.SOAPRequestEnvelope{
|
||||
XmlnsSoapenv: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
XmlnsSiat: "https://siat.impuestos.gob.bo/",
|
||||
Header: &sd.SOAPRequestHeader{},
|
||||
Body: sd.SOAPRequestBody{
|
||||
Operacion: sd.OperacionXML{
|
||||
XMLName: xml.Name{Local: "siat:" + s.TagNames},
|
||||
Solicitud: sd.SolicitudSincronizacion{
|
||||
CodigoAmbiente: solicitud.CodigoAmbiente,
|
||||
CodigoPuntoVenta: solicitud.CodigoPuntoVenta,
|
||||
CodigoSistema: solicitud.CodigoSistema,
|
||||
CodigoSucursal: solicitud.CodigoSucursal,
|
||||
Cuis: solicitud.Cuis,
|
||||
Nit: solicitud.Nit,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Se genera el XML con indentación para mayor legibilidad
|
||||
xmlBytes, err := xml.MarshalIndent(envelope, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error al generar XML: %w", err)
|
||||
}
|
||||
|
||||
// Se concatena la cabecera XML
|
||||
soapRequest := xml.Header + string(xmlBytes)
|
||||
|
||||
return soapRequest, nil
|
||||
}
|
||||
|
||||
// validarSolicitud verifica que los campos requeridos estén presentes en la solicitud
|
||||
//func validarSolicitud(solicitud sd.SolicitudSincronizacionLeyendas) error {
|
||||
// if solicitud.CodigoSistema == "" {
|
||||
// return fmt.Errorf("código de sistema no puede estar vacío")
|
||||
// }
|
||||
// if solicitud.Nit == "0" {
|
||||
// return fmt.Errorf("NIT no puede ser cero")
|
||||
// }
|
||||
// if solicitud.Cuis == "" {
|
||||
// return fmt.Errorf("CUIS no puede estar vacío")
|
||||
// }
|
||||
// if solicitud.CodigoAmbiente == "0" {
|
||||
// return fmt.Errorf("código de ambiente no puede ser cero")
|
||||
// }
|
||||
// return nil
|
||||
//}
|
@ -0,0 +1,48 @@
|
||||
package sincronizacionDatos
|
||||
|
||||
import (
|
||||
"daemonService/internal/models"
|
||||
"daemonService/internal/models/sincronizacionDatos/request"
|
||||
"daemonService/internal/repositories"
|
||||
"daemonService/internal/services/utils"
|
||||
"daemonService/internal/soap"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ProcesarRegistro(s *models.ServiceModel, solicitud request.SolicitudSincronizacion, cuisID int) (interface{}, error) {
|
||||
|
||||
log.Printf("Procesando código sistema: %s", solicitud.CodigoSistema)
|
||||
|
||||
// 1. Generar solicitud SOAP
|
||||
soapReq, err := GenerarSOAPRequest(s, solicitud)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generando SOAP: %v", err)
|
||||
}
|
||||
|
||||
// 2. Enviar solicitud con reintentos
|
||||
resp, bodyBytes, err := soap.EnviarSOAPRequest(s, soapReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error enviando SOAP: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 3. Validar respuesta HTTP
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("código de estado HTTP inválido: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// 4. Parsear respuesta
|
||||
respSincronizacion, err := utils.ProcesarRespuestaSincronizacion(s.TagNames, bodyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. Guardar en base de datos
|
||||
if err := repositories.GuardarRegistro(s, solicitud, cuisID, soapReq, bodyBytes, respSincronizacion); err != nil {
|
||||
return nil, fmt.Errorf("error guardando registro: %v", err)
|
||||
}
|
||||
|
||||
return respSincronizacion, nil
|
||||
}
|
@ -0,0 +1,347 @@
|
||||
package sincronizacionDatos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"daemonService/internal/handlers"
|
||||
"daemonService/internal/models"
|
||||
"daemonService/internal/models/sincronizacionDatos/request"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SincronizacionDatosService struct {
|
||||
models.ServiceModel
|
||||
}
|
||||
|
||||
func (s *SincronizacionDatosService) Execute(ctx context.Context, req *http.Request, isCron bool) (interface{}, error) {
|
||||
|
||||
var nit, codPuntoVenta, codSucursal, codigoSistema string
|
||||
|
||||
if req != nil {
|
||||
//2) Recuperar los query params
|
||||
nit = req.URL.Query().Get("nit")
|
||||
codPuntoVenta = req.URL.Query().Get("code_point_sale")
|
||||
codSucursal = req.URL.Query().Get("code_branch")
|
||||
codigoSistema = req.URL.Query().Get("code_system")
|
||||
log.Printf("nit: %v", nit)
|
||||
log.Printf("codPuntoVenta: %v", codPuntoVenta)
|
||||
log.Printf("codSucursal: %v", codSucursal)
|
||||
log.Printf("codigoSistema: %v", codigoSistema)
|
||||
}
|
||||
|
||||
// 1. Obtener datos de la base de datos
|
||||
dbCtx, dbCancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer dbCancel()
|
||||
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
|
||||
// Verificar si los parámetros nit, codPuntoVenta y codSucursal tienen valores
|
||||
if nit != "" && codPuntoVenta != "" && codSucursal != "" {
|
||||
// Consulta específica con los parámetros proporcionados
|
||||
query := `
|
||||
SELECT
|
||||
r.codigo_ambiente::text,
|
||||
r.codigo_punto_venta::text,
|
||||
r.codigo_sistema,
|
||||
r.codigo_sucursal::text,
|
||||
c.cuis,
|
||||
r.nit,
|
||||
r.token_key,
|
||||
r.token_value,
|
||||
c.id
|
||||
FROM registroEmpresa r
|
||||
JOIN cuis c ON r.id = c.registro_empresa_id
|
||||
WHERE r.nit = $1 AND r.codigo_punto_venta = $2 AND r.codigo_sucursal = $3
|
||||
ORDER BY r.id`
|
||||
|
||||
rows, err = s.DB.QueryContext(dbCtx, query, nit, codPuntoVenta, codSucursal)
|
||||
} else if isCron || req == nil {
|
||||
// Consulta para el caso de CRON (asumiendo que necesitas todos los registros)
|
||||
query := `
|
||||
SELECT
|
||||
r.codigo_ambiente::text,
|
||||
r.codigo_punto_venta::text,
|
||||
r.codigo_sistema,
|
||||
r.codigo_sucursal::text,
|
||||
c.cuis,
|
||||
r.nit,
|
||||
r.token_key,
|
||||
r.token_value,
|
||||
c.id
|
||||
FROM registroEmpresa r
|
||||
JOIN cuis c ON r.id = c.registro_empresa_id
|
||||
ORDER BY r.id`
|
||||
|
||||
rows, err = s.DB.QueryContext(dbCtx, query)
|
||||
} else {
|
||||
// Si no es CRON y faltan parámetros, retornar error
|
||||
return nil, fmt.Errorf("parámetros incompletos: se requiere nit, code_point_sale y code_branch")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, handlers.HandleError(&s.ServiceModel, "Error en consulta SQL: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var respService interface{}
|
||||
|
||||
// 2. Procesar registros
|
||||
success, errors := 0, 0
|
||||
for rows.Next() {
|
||||
var solicitud request.SolicitudSincronizacion
|
||||
var cuisID int
|
||||
var tokenKey string
|
||||
var tokenValue string
|
||||
|
||||
if err := rows.Scan(
|
||||
&solicitud.CodigoAmbiente,
|
||||
&solicitud.CodigoPuntoVenta,
|
||||
&solicitud.CodigoSistema,
|
||||
&solicitud.CodigoSucursal,
|
||||
&solicitud.Cuis,
|
||||
&solicitud.Nit,
|
||||
&tokenKey,
|
||||
&tokenValue,
|
||||
&cuisID,
|
||||
); err != nil {
|
||||
log.Printf("Error escaneando fila: %v", err)
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
|
||||
s.ServiceModel.KeyToken = tokenKey
|
||||
s.ServiceModel.ValueToken = tokenValue
|
||||
respService, err = ProcesarRegistro(&s.ServiceModel, solicitud, cuisID)
|
||||
if err != nil {
|
||||
log.Printf("Error procesando registro: %v", err)
|
||||
errors++
|
||||
} else {
|
||||
success++
|
||||
}
|
||||
|
||||
fmt.Printf("respuesta del servicio: %v", respService)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, handlers.HandleError(&s.ServiceModel, "Error en resultados: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Proceso completado. Éxitos: %d, Errores: %d", success, errors)
|
||||
if errors > 0 {
|
||||
return nil, fmt.Errorf("proceso completado con %d errores", errors)
|
||||
}
|
||||
return respService, nil
|
||||
}
|
||||
|
||||
//func (s *SincronizacionDatosService) Execute(ctx context.Context, req *http.Request, isCron bool) (interface{}, error) {
|
||||
//
|
||||
// //2) Recuperar los query params
|
||||
// nit := req.URL.Query().Get("nit")
|
||||
// codPuntoVenta := req.URL.Query().Get("code_point_sale")
|
||||
// codSucursal := req.URL.Query().Get("code_branch")
|
||||
// codigoSistema := req.URL.Query().Get("code_system")
|
||||
// log.Printf("ni: %v", nit)
|
||||
// log.Printf("codPuntoVenta: %v", codPuntoVenta)
|
||||
// log.Printf("codSucursal: %v", codSucursal)
|
||||
// log.Printf("codigoSistema: %v", codigoSistema)
|
||||
//
|
||||
// // 1. Obtener datos de la base de datos
|
||||
// dbCtx, dbCancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
// defer dbCancel()
|
||||
//
|
||||
// ///////// cuando es CRON = true
|
||||
// query := `
|
||||
// SELECT
|
||||
// r.codigo_ambiente::text,
|
||||
// r.codigo_punto_venta::text,
|
||||
// r.codigo_sistema,
|
||||
// r.codigo_sucursal::text,
|
||||
// c.cuis,
|
||||
// r.nit,
|
||||
// r.token_key,
|
||||
// r.token_value,
|
||||
// c.id
|
||||
// FROM registroEmpresa r
|
||||
// JOIN cuis c ON r.id = c.registro_empresa_id
|
||||
// WHERE r.nit = $1 and r.codigo_punto_venta = $2 and r.codigo_sucursal = $3
|
||||
// ORDER BY r.id`
|
||||
//
|
||||
// rows, err := s.DB.QueryContext(dbCtx, query, nit, codPuntoVenta, codSucursal)
|
||||
// if err != nil {
|
||||
// return nil, handlers.HandleError(&s.ServiceModel, "Error en consulta SQL: %v", err)
|
||||
// }
|
||||
// defer rows.Close()
|
||||
//
|
||||
// var respService interface{}
|
||||
//
|
||||
// // 2. Procesar registros
|
||||
// success, errors := 0, 0
|
||||
// for rows.Next() {
|
||||
// var solicitud request.SolicitudSincronizacion
|
||||
// var cuisID int
|
||||
// var tokenKey string
|
||||
// var tokenValue string
|
||||
//
|
||||
// if err := rows.Scan(
|
||||
// &solicitud.CodigoAmbiente,
|
||||
// &solicitud.CodigoPuntoVenta,
|
||||
// &solicitud.CodigoSistema,
|
||||
// &solicitud.CodigoSucursal,
|
||||
// &solicitud.Cuis,
|
||||
// &solicitud.Nit,
|
||||
// &tokenKey,
|
||||
// &tokenValue,
|
||||
// &cuisID,
|
||||
// ); err != nil {
|
||||
// log.Printf("Error escaneando fila: %v", err)
|
||||
// errors++
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// s.ServiceModel.KeyToken = tokenKey
|
||||
// s.ServiceModel.ValueToken = tokenValue
|
||||
// respService, err = ProcesarRegistro(&s.ServiceModel, solicitud, cuisID)
|
||||
// if err != nil {
|
||||
// log.Printf("Error escaneando fila: %v", err)
|
||||
// }
|
||||
//
|
||||
// fmt.Printf("respuesta del servicio: %v", respService)
|
||||
// }
|
||||
//
|
||||
// if err := rows.Err(); err != nil {
|
||||
// return nil, handlers.HandleError(&s.ServiceModel, "Error en resultados: %v", err)
|
||||
// }
|
||||
//
|
||||
// log.Printf("Proceso completado. Éxitos: %d, Errores: %d", success, errors)
|
||||
// if errors > 0 {
|
||||
// return nil, fmt.Errorf("proceso completado con %d errores", errors)
|
||||
// }
|
||||
// return respService, nil
|
||||
//}
|
||||
|
||||
func (s *SincronizacionDatosService) GetName() string {
|
||||
//func GetName(s models.Service) string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// getEmpresaYCuisID busca registroEmpresa primero por NIT y luego filtra
|
||||
// por puntoVenta y/o sucursal si vienen > 0.
|
||||
func getEmpresaYCuisID(db *sql.DB, nit string, puntoVenta, sucursal int) (empresaID, cuisID int, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Construyo dinámicamente el WHERE
|
||||
where := "nit = $1"
|
||||
args := []interface{}{nit}
|
||||
idx := 2
|
||||
|
||||
if puntoVenta > 0 {
|
||||
where += fmt.Sprintf(" AND codigo_punto_venta = $%d", idx)
|
||||
args = append(args, puntoVenta)
|
||||
idx++
|
||||
}
|
||||
if sucursal > 0 {
|
||||
where += fmt.Sprintf(" AND codigo_sucursal = $%d", idx)
|
||||
args = append(args, sucursal)
|
||||
idx++
|
||||
}
|
||||
|
||||
// Si solo vino nit, dejamos where = "nit = $1"
|
||||
query := fmt.Sprintf("SELECT id FROM registroEmpresa WHERE %s", where)
|
||||
row := db.QueryRowContext(ctx, query, args...)
|
||||
|
||||
if err = row.Scan(&empresaID); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, 0, fmt.Errorf("no existe registroEmpresa para %q (pv=%d, suc=%d)", nit, puntoVenta, sucursal)
|
||||
}
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// Ahora buscamos en cuis
|
||||
row2 := db.QueryRowContext(ctx,
|
||||
`SELECT id FROM cuis WHERE registro_empresa_id = $1 ORDER BY fecha_vigencia DESC LIMIT 1`,
|
||||
empresaID,
|
||||
)
|
||||
if err = row2.Scan(&cuisID); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return empresaID, 0, fmt.Errorf("no hay CUIS para registro_empresa_id=%d", empresaID)
|
||||
}
|
||||
return empresaID, 0, err
|
||||
}
|
||||
|
||||
return empresaID, cuisID, nil
|
||||
}
|
||||
|
||||
// de sincronizar_tipo_moneda para un cuis dado.
|
||||
func getRespJSONTipoMoneda(db *sql.DB, cuisID int) ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rows, err := db.QueryContext(ctx,
|
||||
`SELECT respJson
|
||||
FROM sincronizar_tipo_moneda
|
||||
WHERE cuis_id = $1`,
|
||||
cuisID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var respList []string
|
||||
for rows.Next() {
|
||||
var resp sql.NullString
|
||||
if err := rows.Scan(&resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Valid {
|
||||
respList = append(respList, resp.String)
|
||||
} else {
|
||||
// si fuera NULL, podrías elegir omitirlo o añadir una cadena vacía
|
||||
respList = append(respList, "")
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return respList, nil
|
||||
}
|
||||
|
||||
//// getTipoMoneda trae todas las filas de sincronizar_tipo_moneda para un cuis dado
|
||||
//func getTipoMoneda(db *sql.DB, cuisID int) ([]entities.TipoMoneda, error) {
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
// defer cancel()
|
||||
//
|
||||
// rows, err := db.QueryContext(ctx,
|
||||
// `SELECT id, codigo, reqSoap, respSoap, reqJson, respJson, fecha_creacion, fecha_actualizacion, cuis_id
|
||||
// FROM sincronizar_tipo_moneda
|
||||
// WHERE cuis_id = $1`,
|
||||
// cuisID,
|
||||
// )
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer rows.Close()
|
||||
//
|
||||
// var resultados []entities.TipoMoneda
|
||||
// for rows.Next() {
|
||||
// var tm entities.TipoMoneda
|
||||
// if err := rows.Scan(
|
||||
// &tm.ID, &tm.Codigo, &tm.ReqSoap, &tm.RespSoap,
|
||||
// &tm.ReqJson, &tm.RespJson, &tm.FechaCreacion,
|
||||
// &tm.FechaActualizacion, &tm.CUISID,
|
||||
// ); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// resultados = append(resultados, tm)
|
||||
// }
|
||||
// if err := rows.Err(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return resultados, nil
|
||||
//}
|
125
daemonService/internal/services/utils/sincronizacionDatosUtil.go
Normal file
125
daemonService/internal/services/utils/sincronizacionDatosUtil.go
Normal file
@ -0,0 +1,125 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"daemonService/internal/models/sincronizacionDatos/response"
|
||||
"daemonService/internal/soap"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ProcesarRespuestaSincronizacion(tagName string, bodyBytes []byte) (interface{}, error) {
|
||||
var err error
|
||||
var respSincronizacion interface{}
|
||||
|
||||
switch tagName {
|
||||
case "sincronizarActividades":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.ListaActividades, response.SoapEnvelopeActividad](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta ListaActividades: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarFechaHora":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAPfechaHora[response.RespuestaFechaHora, response.SoapEnvelopeFechaHora](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta FechaHora: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarListaMensajesServicios":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.MensajeServicio, response.SoapEnvelopeMensajeServicio](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta MensajeServicio: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarListaLeyendasFactura":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAPIdentificador[[]response.LeyendaDetalle, response.SoapEnvelopeLeyendas](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta LeyendaDetalle: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarListaProductosServicios":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.ProductoServicio, response.SoapEnvelopeProductoServicio](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta ProductoServicio: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaMotivoAnulacion":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.MotivoAnulacion, response.SoapEnvelopeMotivoAnulacion](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta MotivoAnulacion: %v", err)
|
||||
}
|
||||
case "sincronizarParametricaTipoDocumentoIdentidad":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.TipoDocumento, response.SoapEnvelopeTipoDocumento](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta TipoDocumento: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaTipoDocumentoSector":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.TipoSector, response.SoapEnvelopeTipoSector](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta TipoSector: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarListaActividadesDocumentoSector":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.TipoActividadesSector, response.SoapEnvelopeTipoActividadesSector](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta TipoSctividadSector: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaTipoEmision":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.TipoEmision, response.SoapEnvelopeTipoEmision](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta TipoEmision: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaTiposFactura":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.TipoFactura, response.SoapEnvelopeTipoFactura](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta TiposFactura: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaTipoMoneda":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.Moneda, response.SoapEnvelopeMoneda](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta Moneda: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaTipoMetodoPago":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.MetodoPago, response.SoapEnvelopeMetodoPago](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta MetodoPago: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaTipoPuntoVenta":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.PuntoVenta, response.SoapEnvelopePuntoVenta](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta PuntoVenta: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaUnidadMedida":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.UnidadMedida, response.SoapEnvelopeUnidadMedida](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta UnidadMedida: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaEventosSignificativos":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.EventoSignificativo, response.SoapEnvelopeEventoSignificativo](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta EventosSignificativos: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaTipoHabitacion":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.TipoHabitacion, response.SoapEnvelopeTipoHabitacion](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta TipoHabitacion: %v", err)
|
||||
}
|
||||
|
||||
case "sincronizarParametricaPaisOrigen":
|
||||
respSincronizacion, err = soap.ParsearRespuestaSOAP[[]response.PaisOrigen, response.SoapEnvelopePaisOrigen](bodyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parseando respuesta PaisOrigen: %v", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("tag no reconocido: %s", tagName)
|
||||
}
|
||||
|
||||
return respSincronizacion, nil
|
||||
}
|
59
daemonService/internal/soap/enviarSOAPRequest.go
Normal file
59
daemonService/internal/soap/enviarSOAPRequest.go
Normal file
@ -0,0 +1,59 @@
|
||||
package soap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"daemonService/internal/models"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// func (s *ListaLeyendasFacturaService) enviarSOAPRequest(soapReq string) (*http.Response, []byte, error) {
|
||||
func EnviarSOAPRequest(s *models.ServiceModel, soapReq string) (*http.Response, []byte, error) {
|
||||
var resp *http.Response
|
||||
var bodyBytes []byte
|
||||
var err error
|
||||
|
||||
backoff := time.Second
|
||||
for i := 0; i < s.SOAPRetries; i++ {
|
||||
if i > 0 {
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", s.SOAPEndpoint, bytes.NewBufferString(soapReq))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "text/xml; charset=utf-8")
|
||||
//req.Header.Set("apikey", s.APIKey)
|
||||
req.Header.Set(s.KeyToken, s.ValueToken)
|
||||
|
||||
// Crea un transporte que ignore la configuración de proxy
|
||||
transport := &http.Transport{
|
||||
Proxy: nil, // Esto desactiva el uso de proxy
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
resp, err = client.Do(req)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
continue
|
||||
}
|
||||
|
||||
bodyBytes, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
resp.Body.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
return resp, bodyBytes, nil
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("fallo después de %d reintentos: %v", s.SOAPRetries, err)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user