fix: correção no modelo tecnospeed
This commit is contained in:
commit
343b30a19f
31 changed files with 2909 additions and 0 deletions
216
.gitignore
vendored
Normal file
216
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[codz]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# UV
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
#uv.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
#poetry.toml
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||||
|
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||||
|
#pdm.lock
|
||||||
|
#pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# pixi
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||||
|
#pixi.lock
|
||||||
|
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||||
|
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||||
|
.pixi
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.envrc
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
system_config.json
|
||||||
|
inserts_teste.sql
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
||||||
|
# Abstra
|
||||||
|
# Abstra is an AI-powered process automation framework.
|
||||||
|
# Ignore directories containing user credentials, local state, and settings.
|
||||||
|
# Learn more at https://abstra.io/docs
|
||||||
|
.abstra/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||||
|
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||||
|
# you could uncomment the following to ignore the entire vscode folder
|
||||||
|
# .vscode/
|
||||||
|
|
||||||
|
# Ruff stuff:
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# PyPI configuration file
|
||||||
|
.pypirc
|
||||||
|
|
||||||
|
# Cursor
|
||||||
|
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
||||||
|
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
||||||
|
# refer to https://docs.cursor.com/context/ignore-files
|
||||||
|
.cursorignore
|
||||||
|
.cursorindexingignore
|
||||||
|
|
||||||
|
# Marimo
|
||||||
|
marimo/_static/
|
||||||
|
marimo/_lsp/
|
||||||
|
__marimo__/
|
||||||
|
|
||||||
|
# Diretorio de classes acessorias
|
||||||
|
Model/
|
||||||
|
utils/
|
||||||
|
logs/
|
||||||
|
src/models/sg_tray_models/
|
||||||
|
src/models/servico_titulos_receber/
|
||||||
34
Dockerfile
Normal file
34
Dockerfile
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
FROM python:3.12-slim AS builder
|
||||||
|
ENV LANG=C.UTF-8 \
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
locales vim iputils-ping build-essential && \
|
||||||
|
locale-gen en_US.UTF-8 && \
|
||||||
|
apt-get clean && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* && \
|
||||||
|
pip install --no-cache-dir unidecode
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
FROM python:3.12-slim
|
||||||
|
ENV LANG=C.UTF-8 \
|
||||||
|
LC_ALL=C.UTF-8
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
locales vim iputils-ping && \
|
||||||
|
locale-gen en_US.UTF-8 && \
|
||||||
|
apt-get clean && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
||||||
|
COPY --from=builder /usr/local/bin /usr/local/bin
|
||||||
|
|
||||||
|
RUN mkdir /app/logs
|
||||||
|
RUN mkdir /app/controller
|
||||||
|
COPY src .
|
||||||
|
COPY utils/Exchange2.py utils/exchangeModels.py utils/HostMetrics.py /app
|
||||||
|
# COPY Model/sg_tray_models/services_model.py /app/models/sg_tray_models/
|
||||||
|
# COPY Model/servico_titulos_receber/contract_Tittles.py /app/models/servico_titulos_receber/
|
||||||
|
# COPY Model/servico_titulos_receber/tittle_model.py /app/models/servico_titulos_receber/
|
||||||
|
|
||||||
|
CMD ["python", "controller.py"]
|
||||||
75
README.md
Normal file
75
README.md
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Innovar AMR Tray Controller
|
||||||
|
|
||||||
|
Microserviço responsável por atuar como controlador de integrações entre a plataforma de e-commerce Tray, o emissor de NFe Plugnotas e o ERP Sienge.
|
||||||
|
|
||||||
|
## Visão Geral
|
||||||
|
|
||||||
|
Este projeto consome mensagens de uma fila RabbitMQ (originadas de webhooks) e orquestra a comunicação entre diferentes sistemas para automatizar o fluxo de vendas:
|
||||||
|
|
||||||
|
1. **Tray (E-commerce):**
|
||||||
|
- Recebe webhooks de criação (`insert`) e atualização (`update`) de pedidos.
|
||||||
|
- Busca dados completos do pedido na API da Tray.
|
||||||
|
- Atualiza dados fiscais (NFe) no pedido dentro da Tray após faturamento.
|
||||||
|
|
||||||
|
2. **Plugnotas (Notas Fiscais):**
|
||||||
|
- Envia dados do pedido para emissão de Nota Fiscal Eletrônica.
|
||||||
|
- Recebe webhooks informando a conclusão da emissão da NFe, extraindo a chave de acesso, o número e os produtos a partir do XML retornado (base64).
|
||||||
|
|
||||||
|
3. **Sienge (ERP - Integração via Filas/RPC):**
|
||||||
|
- **Clientes:** Envia requisições (RPC) para criação de clientes no Sienge quando um novo pedido é gerado.
|
||||||
|
- **Títulos a Receber:** Envia requisições (RPC) para geração de títulos financeiros no Sienge após a emissão da NFe.
|
||||||
|
|
||||||
|
## Fluxo de Processamento
|
||||||
|
|
||||||
|
### 1. Webhook Tray (`Recebimento`)
|
||||||
|
- **Ação `insert`:** O controlador identifica o novo pedido, cria/sincroniza o cliente no ERP Sienge e atualiza o ID do cliente no banco de dados local.
|
||||||
|
- **Ação `update`:** Caso o pedido passe para o status "A ENVIAR", o serviço envia os dados mapeados para a fila de emissão de NFe, sinalizando no banco local que o envio ocorreu.
|
||||||
|
|
||||||
|
### 2. Webhook Plugnotas (`RecebimentoPlugnotas`)
|
||||||
|
- Ao receber a confirmação de que a NFe foi gerada com status "CONCLUIDO":
|
||||||
|
- Decodifica e extrai as informações pertinentes da nota a partir do XML da NFe.
|
||||||
|
- Envia a ordem para criar o título a receber no ERP (Sienge).
|
||||||
|
- Atualiza a nota fiscal (chave, link, xml) diretamente no painel da Tray.
|
||||||
|
- Grava o status atualizado no banco de dados local.
|
||||||
|
|
||||||
|
## Tecnologias e Bibliotecas
|
||||||
|
|
||||||
|
- **Python 3**
|
||||||
|
- **Bibliotecas Principais:** Pydantic (validação de dados), SQLAlchemy/Psycopg2 (banco de dados), Redis, Requests, Pika (RabbitMQ).
|
||||||
|
- **Mensageria:** RabbitMQ para filas assíncronas e RPC, facilitado pelas bibliotecas base da equipe (`Exchange2`).
|
||||||
|
- **Banco de Dados:** PostgreSQL (Persistência de estado dos pedidos, status de integrações e logs).
|
||||||
|
- **Cache / Configuração:** Redis (Armazenamento e busca das credenciais e configurações de ambiente em tempo real).
|
||||||
|
- **Infraestrutura:** Docker & Docker Compose.
|
||||||
|
|
||||||
|
## Como Executar
|
||||||
|
|
||||||
|
### Pré-requisitos
|
||||||
|
- Docker e Docker Compose instalados no ambiente de execução.
|
||||||
|
- Acesso aos repositórios Git base (`Exchange2` e `SoftGOModel`) pois o script de build fará o clone de dependências proprietárias.
|
||||||
|
- Instâncias do Redis, RabbitMQ e PostgreSQL acessíveis na rede (`sg_base_network`).
|
||||||
|
|
||||||
|
### Passos para Build e Deploy
|
||||||
|
|
||||||
|
1. Clone o repositório e navegue até a pasta raiz.
|
||||||
|
2. Execute o script de build para baixar as dependências e criar a imagem Docker:
|
||||||
|
```bash
|
||||||
|
chmod +x buildImages.sh
|
||||||
|
./buildImages.sh
|
||||||
|
```
|
||||||
|
3. Inicie o container com o Docker Compose:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variáveis de Ambiente e Configurações
|
||||||
|
|
||||||
|
O container do `controller` possui poucas configurações no `docker-compose.yml`, pois o sistema busca suas credenciais principais (banco, AMQP, endpoints, chaves de API da Tray, etc.) diretamente de uma chave JSON no **Redis** (`innovar_amr_tray-system_config-innovar`).
|
||||||
|
Certifique-se de que essa chave JSON esteja corretamente populada no Redis para que o microserviço consiga inicializar.
|
||||||
|
|
||||||
|
## Estrutura de Diretórios
|
||||||
|
|
||||||
|
- `src/controller.py`: Ponto de entrada do serviço e consumidor principal da fila RabbitMQ.
|
||||||
|
- `src/services/`: Regras de negócio, transformações e mapeamentos (Tray para Sienge, Tray para NFe).
|
||||||
|
- `src/models/`: Definições Pydantic para validação das estruturas de dados.
|
||||||
|
- `src/api/`: Comunicação HTTP direta com APIs externas.
|
||||||
|
- `src/db/`: Instância e métodos de comunicação com o PostgreSQL local.
|
||||||
96
buildImages.sh
Normal file
96
buildImages.sh
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
version=':v1' # VERSIONAMENTO MACRO - TODOS SERVIÇOS NA MESMA VERSÃO SÃO COMPATÍVEIS ENTRE SI
|
||||||
|
|
||||||
|
nome_diretorio="innovar_amr_tray" #$(basename "$PWD")
|
||||||
|
system="${nome_diretorio,,}"
|
||||||
|
SYSTEM="${system^^}"
|
||||||
|
|
||||||
|
# Try to find the correct $DOCKER_COMPOSE_CMD command
|
||||||
|
if command -v docker-compose &> /dev/null; then
|
||||||
|
DOCKER_COMPOSE_CMD="docker-compose"
|
||||||
|
elif command -v docker &> /dev/null && docker compose version &> /dev/null; then
|
||||||
|
DOCKER_COMPOSE_CMD="docker compose"
|
||||||
|
else
|
||||||
|
echo "$DOCKER_COMPOSE_CMD is not installed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
echo "VERSAO ATUAL CLASSES ACESSORIAS Exchange2 SoftGOModel"
|
||||||
|
$DOCKER_COMPOSE_CMD down
|
||||||
|
|
||||||
|
if [ -d "utils" ]; then
|
||||||
|
rm -rf utils
|
||||||
|
fi
|
||||||
|
if [ -d "Model" ]; then
|
||||||
|
rm -rf Model
|
||||||
|
fi
|
||||||
|
|
||||||
|
# PROJETOS DE BASE
|
||||||
|
git clone git@github.com:desenvolvimentopsa/Exchange2.git utils/Exchange2
|
||||||
|
git clone git@github.com:desenvolvimentopsa/SoftGOModel.git Model
|
||||||
|
cd utils
|
||||||
|
find . -name "*.py" | xargs -I {} cp {} .
|
||||||
|
rm -rf Exchange2 SOFTGO_AUTH_MIDDLEWARE
|
||||||
|
cd ..
|
||||||
|
# remove .git e .gitignore do Model
|
||||||
|
cd Model
|
||||||
|
find . \( -name ".git" -o -name ".gitignore" \) -exec rm -rf {} +
|
||||||
|
cd ..
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ -n "$arg" ] && [ "$arg" == "git" ]; then
|
||||||
|
echo "VERSAO ATUAL DO GIT ${version}"
|
||||||
|
git stash
|
||||||
|
git pull
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -d "utils" ] || [ ! -d "Model" ]; then
|
||||||
|
echo "DOWNLOAD CLASSES ACESSORIAS"
|
||||||
|
clone
|
||||||
|
else
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ -n "$arg" ] && [ "$arg" == "clone" ]; then
|
||||||
|
echo "DOWNLOAD CLASSES ACESSORIAS"
|
||||||
|
clone
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ -n "$arg" ] && [ "$arg" == "prune" ]; then
|
||||||
|
echo "REMOVER IMAGENS ${system} ${version}"
|
||||||
|
docker image ls -q -f "reference=${system}-*${version}" | xargs -I {} docker image rm -f {}
|
||||||
|
docker system prune -af
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# EDITE DE ACORDO COM OS SERVIÇOS QUE VAI IMPLEMENTAR
|
||||||
|
# NOME DO DIRETORIO É O NOME DO SISTEMA E CADA DIRETORIO UM SERVICO
|
||||||
|
# CASO UM UNICO SERVIÇO MANTENHA O MODELO PARA PODERMOS IMPLEMENTAR UM DEPLOY AUTOMATICO NO FUTURO
|
||||||
|
#==> MODELO
|
||||||
|
echo "GERAR IMAGENS ${system} ${version}"
|
||||||
|
|
||||||
|
service="controller"
|
||||||
|
echo $service
|
||||||
|
echo "COPIAR ARQUIVOS PARA BUILD ${system} ${version}"
|
||||||
|
cp -r Model/sg_tray_models ./src/models/sg_tray_models/
|
||||||
|
cp -r Model/servico_titulos_receber ./src/models/servico_titulos_receber/
|
||||||
|
|
||||||
|
docker build -t ${system}-${service}${version} .
|
||||||
|
rm -rf ./src/models/sg_tray_models/*
|
||||||
|
rm -rf ./src/models/servico_titulos_receber/*
|
||||||
|
|
||||||
|
cd ./src
|
||||||
|
|
||||||
|
ln -sf ../utils/Exchange2.py .
|
||||||
|
ln -sf ../utils/exchangeModels.py .
|
||||||
|
ln -sf ../utils/HostMetrics.py .
|
||||||
|
ln -sf ../Model/sg_tray_models/services_model.py ./models/sg_tray_models/services_model.py
|
||||||
|
ln -sf ../Model/servico_titulos_receber/contract_Tittles.py ./models/servico_titulos_receber/contract_Tittles.py
|
||||||
|
ln -sf ../Model/servico_titulos_receber/tittle_model.py ./models/servico_titulos_receber/tittle_model.py
|
||||||
|
|
||||||
|
cd ..
|
||||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
networks:
|
||||||
|
local:
|
||||||
|
sg_base_network:
|
||||||
|
name: sg_base_network
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
controller:
|
||||||
|
image: innovar_amr_tray-controller:v1
|
||||||
|
#restart: always
|
||||||
|
volumes:
|
||||||
|
- ./logs/controller:/app/logs
|
||||||
|
- /etc/hostname:/app/hostname
|
||||||
|
- /proc:/app/host-proc
|
||||||
|
- /metrics:/app/metrics
|
||||||
|
environment:
|
||||||
|
- "TZ=America/Sao_Paulo"
|
||||||
|
networks:
|
||||||
|
- local
|
||||||
|
- sg_base_network
|
||||||
28
envio_cliente.json
Normal file
28
envio_cliente.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"usuario": "api_user",
|
||||||
|
"senha": "api_password",
|
||||||
|
"subdominio": "empresa_exemplo"
|
||||||
|
},
|
||||||
|
"cliente": {
|
||||||
|
"naturalPersonData": {
|
||||||
|
"name": "João da Silva",
|
||||||
|
"cpf": "12345678901",
|
||||||
|
"email": "joao@exemplo.com",
|
||||||
|
"birthDate": "1990-01-01",
|
||||||
|
"civilStatus": "Casado",
|
||||||
|
"profession": "Engenheiro"
|
||||||
|
},
|
||||||
|
"addresses": [
|
||||||
|
{
|
||||||
|
"type": "RES",
|
||||||
|
"streetName": "Rua Exemplo",
|
||||||
|
"number": "123",
|
||||||
|
"neighborhood": "Centro",
|
||||||
|
"city": "São Paulo",
|
||||||
|
"state": "SP",
|
||||||
|
"zipCode": "01001000"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
88
modelo_envio_plugnotas.json
Normal file
88
modelo_envio_plugnotas.json
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
{
|
||||||
|
"fila": "nfe",
|
||||||
|
"metodo": "post",
|
||||||
|
"cliente": "{{cliente}}",
|
||||||
|
"dados": {
|
||||||
|
"idIntegracao": "NFe_TESTE_001",
|
||||||
|
"presencial": true,
|
||||||
|
"consumidorFinal": true,
|
||||||
|
"natureza": "OPERAÇÃO INTERNA",
|
||||||
|
"emitente": {
|
||||||
|
"cpfCnpj": "00000000000000",
|
||||||
|
"inscricaoEstadual": "ISENTO"
|
||||||
|
},
|
||||||
|
"destinatario": {
|
||||||
|
"cpfCnpj": "08114280956",
|
||||||
|
"razaoSocial": "NF-E EMITIDA EM HOMOLOGACAO",
|
||||||
|
"email": "contato@tecnospeed.com.br",
|
||||||
|
"endereco": {
|
||||||
|
"logradouro": "AVENIDA DUQUE DE CAXIAS",
|
||||||
|
"numero": "882",
|
||||||
|
"bairro": "CENTRO",
|
||||||
|
"codigoCidade": "4115200",
|
||||||
|
"descricaoCidade": "MARINGA",
|
||||||
|
"estado": "PR",
|
||||||
|
"cep": "87020025"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"itens": [
|
||||||
|
{
|
||||||
|
"codigo": "1",
|
||||||
|
"descricao": "PRODUTO DE TESTE",
|
||||||
|
"ncm": "06029090",
|
||||||
|
"cest": "0123456",
|
||||||
|
"cfop": "5101",
|
||||||
|
"valorUnitario": {
|
||||||
|
"comercial": 4.67,
|
||||||
|
"tributavel": 4.67
|
||||||
|
},
|
||||||
|
"valor": 4.67,
|
||||||
|
"tributos": {
|
||||||
|
"icms": {
|
||||||
|
"origem": "0",
|
||||||
|
"cst": "00",
|
||||||
|
"baseCalculo": {
|
||||||
|
"modalidadeDeterminacao": 0,
|
||||||
|
"valor": 0
|
||||||
|
},
|
||||||
|
"aliquota": 0,
|
||||||
|
"valor": 0
|
||||||
|
},
|
||||||
|
"pis": {
|
||||||
|
"cst": "99",
|
||||||
|
"baseCalculo": {
|
||||||
|
"valor": 0,
|
||||||
|
"quantidade": 0
|
||||||
|
},
|
||||||
|
"aliquota": 0,
|
||||||
|
"valor": 0
|
||||||
|
},
|
||||||
|
"cofins": {
|
||||||
|
"cst": "07",
|
||||||
|
"baseCalculo": {
|
||||||
|
"valor": 0
|
||||||
|
},
|
||||||
|
"aliquota": 0,
|
||||||
|
"valor": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pagamentos": [
|
||||||
|
{
|
||||||
|
"aVista": true,
|
||||||
|
"meio": "01",
|
||||||
|
"valor": 4.67
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responsavelTecnico": {
|
||||||
|
"cpfCnpj": "08187168000160",
|
||||||
|
"nome": "Tecnospeed",
|
||||||
|
"email": "contato@tecnospeed.com.br",
|
||||||
|
"telefone": {
|
||||||
|
"ddd": "44",
|
||||||
|
"numero": "30379500"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
modelo_webhook_plugnotas.json
Normal file
7
modelo_webhook_plugnotas.json
Normal file
File diff suppressed because one or more lines are too long
16
payload_nfe_tray.json
Normal file
16
payload_nfe_tray.json
Normal file
File diff suppressed because one or more lines are too long
262
pedido_completo.json
Normal file
262
pedido_completo.json
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
{
|
||||||
|
"Order": {
|
||||||
|
"status": "A ENVIAR VINDI",
|
||||||
|
"id": "12797",
|
||||||
|
"date": "2025-10-01",
|
||||||
|
"hour": "09:07:57",
|
||||||
|
"customer_id": "11531",
|
||||||
|
"partial_total": "69.90",
|
||||||
|
"taxes": "0.00",
|
||||||
|
"discount": "0.00",
|
||||||
|
"point_sale": "PARTICULAR",
|
||||||
|
"shipment": "Retirada",
|
||||||
|
"shipment_value": "0.00",
|
||||||
|
"original_shipment_value": "0.00",
|
||||||
|
"shipment_date": "",
|
||||||
|
"delivered": "",
|
||||||
|
"delivered_status": "",
|
||||||
|
"shipping_cancelled": "0",
|
||||||
|
"store_note": "",
|
||||||
|
"customer_note": "",
|
||||||
|
"partner_id": "",
|
||||||
|
"discount_coupon": "",
|
||||||
|
"client_ip": "",
|
||||||
|
"payment_method_rate": "0.00",
|
||||||
|
"installment": "0",
|
||||||
|
"value_1": "0.00",
|
||||||
|
"sending_code": "",
|
||||||
|
"sending_date": "0000-00-00",
|
||||||
|
"billing_address": "",
|
||||||
|
"delivery_time": "",
|
||||||
|
"payment_method_id": "",
|
||||||
|
"payment_method": "PIX",
|
||||||
|
"session_id": "0BBB15A404B6BA1",
|
||||||
|
"total": "69.90",
|
||||||
|
"payment_date": "2025-10-01",
|
||||||
|
"access_code": "834E9A51CFF9D99",
|
||||||
|
"shipment_integrator": "",
|
||||||
|
"modified": "2025-10-30 10:22:38",
|
||||||
|
"printed": "",
|
||||||
|
"interest": "0.00",
|
||||||
|
"cart_additional_values_discount": "0.00",
|
||||||
|
"cart_additional_values_increase": "0.00",
|
||||||
|
"id_quotation": "",
|
||||||
|
"estimated_delivery_date": "2025-10-01",
|
||||||
|
"is_traceable": "0",
|
||||||
|
"external_code": "",
|
||||||
|
"tracking_url": "",
|
||||||
|
"has_payment": "1",
|
||||||
|
"has_shipment": "0",
|
||||||
|
"has_invoice": "0",
|
||||||
|
"delivery_date": "",
|
||||||
|
"dc_id": "",
|
||||||
|
"dc_order_origin": "",
|
||||||
|
"total_comission_user": "0.00",
|
||||||
|
"total_comission": "0.00",
|
||||||
|
"OrderStatus": {
|
||||||
|
"id": "995",
|
||||||
|
"default": "0",
|
||||||
|
"type": "open",
|
||||||
|
"show_backoffice": "1",
|
||||||
|
"allow_edit_order": "1",
|
||||||
|
"description": "",
|
||||||
|
"status": "A ENVIAR VINDI",
|
||||||
|
"show_status_central": "1",
|
||||||
|
"background": "#60e163",
|
||||||
|
"display_name": "A enviar Vindi",
|
||||||
|
"font_color": "#000000"
|
||||||
|
},
|
||||||
|
"PickupLocation": [],
|
||||||
|
"cost": "0.00",
|
||||||
|
"app_id": "0",
|
||||||
|
"urls": {
|
||||||
|
"payment": "https://1225878.commercesuite.com.br/loja/pagamento.php?loja=1225878&pedido=834E9A51CFF9D99"
|
||||||
|
},
|
||||||
|
"store_segment": "Animais & petshop",
|
||||||
|
"payment_method_type": "",
|
||||||
|
"Customer": {
|
||||||
|
"cnpj": "",
|
||||||
|
"newsletter": "0",
|
||||||
|
"created": "2025-10-01 08:05:12",
|
||||||
|
"terms": "0000-00-00 00:00:00",
|
||||||
|
"id": "11531",
|
||||||
|
"name": "Henrique Crespilho Ferro",
|
||||||
|
"registration_date": "2025-10-01",
|
||||||
|
"rg": "",
|
||||||
|
"cpf": "04650716004",
|
||||||
|
"phone": "14991239292",
|
||||||
|
"cellphone": "14991239292",
|
||||||
|
"birth_date": "",
|
||||||
|
"gender": "0",
|
||||||
|
"email": "gazelle30064@aminating.com",
|
||||||
|
"nickname": "",
|
||||||
|
"token": "834E9A51CFF9D99",
|
||||||
|
"total_orders": "0",
|
||||||
|
"observation": "",
|
||||||
|
"type": "0",
|
||||||
|
"foreign": "0",
|
||||||
|
"company_name": "",
|
||||||
|
"state_inscription": "",
|
||||||
|
"reseller": "",
|
||||||
|
"discount": "0.00",
|
||||||
|
"blocked": "0",
|
||||||
|
"credit_limit": "0.00",
|
||||||
|
"indicator_id": "0",
|
||||||
|
"profile_customer_id": "1",
|
||||||
|
"last_sending_newsletter": "0000-00-00",
|
||||||
|
"last_purchase": "2025-10-01",
|
||||||
|
"last_visit": "2025-10-01",
|
||||||
|
"last_modification": "0000-00-00 00:00:00",
|
||||||
|
"address": "Av. Dr. Quinzinho",
|
||||||
|
"zip_code": "17210-110",
|
||||||
|
"number": "511",
|
||||||
|
"complement": "Estacionamento Jaú Shopping",
|
||||||
|
"neighborhood": "Vila Assis",
|
||||||
|
"city": "Jaú",
|
||||||
|
"state": "SP",
|
||||||
|
"country": "Brasil",
|
||||||
|
"modified": "2025-10-01 09:07:57",
|
||||||
|
"count_orders": "0",
|
||||||
|
"Extensions": {
|
||||||
|
"Profile": {
|
||||||
|
"id": "1",
|
||||||
|
"name": "Padrao",
|
||||||
|
"approves_registration": "0"
|
||||||
|
},
|
||||||
|
"Profiles": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"price_list_id": "0",
|
||||||
|
"name": "Padrao",
|
||||||
|
"approves_registration": "0",
|
||||||
|
"show_price": "0",
|
||||||
|
"theme_id": "0",
|
||||||
|
"selected": "1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CustomerAddresses": [
|
||||||
|
{
|
||||||
|
"CustomerAddress": {
|
||||||
|
"id": "9285",
|
||||||
|
"customer_id": "11531",
|
||||||
|
"address": "Av. Dr. Quinzinho",
|
||||||
|
"number": "511",
|
||||||
|
"complement": "Estacionamento Jaú Shopping",
|
||||||
|
"neighborhood": "Vila Assis",
|
||||||
|
"city": "Jaú",
|
||||||
|
"state": "SP",
|
||||||
|
"zip_code": "17210-110",
|
||||||
|
"country": "Brasil",
|
||||||
|
"type": "1",
|
||||||
|
"active": "1",
|
||||||
|
"description": "",
|
||||||
|
"recipient": "",
|
||||||
|
"type_delivery": "1",
|
||||||
|
"not_list": "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ProductsSold": [
|
||||||
|
{
|
||||||
|
"ProductsSold": {
|
||||||
|
"product_kit_id": "0",
|
||||||
|
"product_kit_id_kit": "0",
|
||||||
|
"id_campaign": "",
|
||||||
|
"product_id": "63",
|
||||||
|
"quantity": "1",
|
||||||
|
"id": "16113",
|
||||||
|
"order_id": "12797",
|
||||||
|
"name": "MMM Calça de Malha Foil Preta Brilhante<br /><strong>Cor</strong> PRETO<br /><strong>TAMANHO</strong> 12 (Disponibilidade: Imediata)<br />",
|
||||||
|
"original_name": "",
|
||||||
|
"virtual_product": "0",
|
||||||
|
"ean": "",
|
||||||
|
"availability_days": "",
|
||||||
|
"availability": "",
|
||||||
|
"Sku": [],
|
||||||
|
"price": "69.90",
|
||||||
|
"cost_price": "0.00",
|
||||||
|
"original_price": "69.90",
|
||||||
|
"weight": "136",
|
||||||
|
"weight_cubic": "110",
|
||||||
|
"brand": "kamylus INV",
|
||||||
|
"model": "",
|
||||||
|
"reference": "",
|
||||||
|
"length": "24",
|
||||||
|
"width": "22",
|
||||||
|
"height": "1",
|
||||||
|
"variant_id": "1132951",
|
||||||
|
"additional_information": "",
|
||||||
|
"text_variant": "",
|
||||||
|
"warranty": "",
|
||||||
|
"bought_together_id": "0",
|
||||||
|
"ncm": "",
|
||||||
|
"included_items": "",
|
||||||
|
"release_date": "",
|
||||||
|
"commissioner_value": "",
|
||||||
|
"comissao": "0.00",
|
||||||
|
"is_giveaway_by_coupon": "0",
|
||||||
|
"ProductSoldImage": [],
|
||||||
|
"Category": [],
|
||||||
|
"is_giveaway": "0",
|
||||||
|
"BoughtTogether": [],
|
||||||
|
"ProductSoldPackage": [],
|
||||||
|
"ProductSoldCard": [],
|
||||||
|
"url": {
|
||||||
|
"http": "",
|
||||||
|
"https": ""
|
||||||
|
},
|
||||||
|
"Discount": [],
|
||||||
|
"Stock": {
|
||||||
|
"id": "1",
|
||||||
|
"name": "Loja"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"OrderInvoice": [],
|
||||||
|
"Payment": [
|
||||||
|
{
|
||||||
|
"Payment": {
|
||||||
|
"created": "2025-10-01 09:24:13",
|
||||||
|
"modified": "2025-10-01 09:24:12",
|
||||||
|
"id": "2691",
|
||||||
|
"order_id": "12797",
|
||||||
|
"payment_method_id": "",
|
||||||
|
"method": "PIX",
|
||||||
|
"payment_place": "",
|
||||||
|
"value": "66.41",
|
||||||
|
"date": "2025-10-01",
|
||||||
|
"note": "Pagamento realizado com sucesso utilizando ASSAS com o metodo de pagamento PIX",
|
||||||
|
"unique_number": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MlOrder": [],
|
||||||
|
"MarketplaceOrder": [],
|
||||||
|
"OrderTransactions": [],
|
||||||
|
"OrderInvoiceAmount": {
|
||||||
|
"reference": "2025-10-01",
|
||||||
|
"value": "0.70",
|
||||||
|
"tax_type": "%",
|
||||||
|
"tax_value": "1.00",
|
||||||
|
"point_sale": "PARTICULAR"
|
||||||
|
},
|
||||||
|
"OtherInvoiceAmounts": [],
|
||||||
|
"ExtraTabs": [],
|
||||||
|
"OrderChilds": [],
|
||||||
|
"interest_paid_by": "seller",
|
||||||
|
"DistributionCenter": {
|
||||||
|
"status": "pending",
|
||||||
|
"type": "main_order",
|
||||||
|
"Package": ""
|
||||||
|
},
|
||||||
|
"PaymentMethodMessage": [],
|
||||||
|
"partner_name": "",
|
||||||
|
"Transaction": ""
|
||||||
|
},
|
||||||
|
"Extensions": [],
|
||||||
|
"User": [],
|
||||||
|
"Confirmation": []
|
||||||
|
}
|
||||||
21
requirements.txt
Normal file
21
requirements.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
annotated-types==0.7.0
|
||||||
|
certifi==2026.2.25
|
||||||
|
cffi==2.0.0
|
||||||
|
charset-normalizer==3.4.7
|
||||||
|
cryptography==46.0.7
|
||||||
|
cryptograpy==0.0.0
|
||||||
|
greenlet==3.5.0
|
||||||
|
idna==3.11
|
||||||
|
pika==1.3.2
|
||||||
|
psutil==7.2.2
|
||||||
|
psycopg2-binary==2.9.12
|
||||||
|
pycparser==3.0
|
||||||
|
pydantic==2.13.2
|
||||||
|
pydantic_core==2.46.2
|
||||||
|
pytz==2026.1.post1
|
||||||
|
redis==7.4.0
|
||||||
|
requests==2.33.1
|
||||||
|
SQLAlchemy==2.0.49
|
||||||
|
typing-inspection==0.4.2
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
urllib3==2.6.3
|
||||||
1
src/Exchange2.py
Symbolic link
1
src/Exchange2.py
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../utils/Exchange2.py
|
||||||
1
src/HostMetrics.py
Symbolic link
1
src/HostMetrics.py
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../utils/HostMetrics.py
|
||||||
15
src/api/publicas.py
Normal file
15
src/api/publicas.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
|
def busca_cep(cep: str) -> dict:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
url = f"https://viacep.com.br/ws/{cep}/json/"
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
time.sleep(0.1)
|
||||||
|
except Exception as e:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
104
src/api/tray.py
Normal file
104
src/api/tray.py
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
from models.sg_tray_models.services_model import TrayOrderRootResponse
|
||||||
|
from appIC2Builder import SystemConfig
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import redis
|
||||||
|
import json
|
||||||
|
|
||||||
|
class ApiTray:
|
||||||
|
def __init__(self, system_config: SystemConfig, redis_client: redis.Redis):
|
||||||
|
self.consumer_key = system_config.tray_consumer_key
|
||||||
|
self.consumer_secret = system_config.tray_consumer_secret
|
||||||
|
self.code = system_config.tray_code
|
||||||
|
self.url = system_config.tray_url
|
||||||
|
self.redis_client = redis_client
|
||||||
|
self.service = system_config.service
|
||||||
|
self.system = system_config.system
|
||||||
|
|
||||||
|
def auth(self) -> str:
|
||||||
|
try:
|
||||||
|
print("\n")
|
||||||
|
print("Autenticando na API da Tray...", flush=True)
|
||||||
|
response = requests.post(url = f"{self.url}/web_api/auth",
|
||||||
|
headers = {"Content-Type": "application/x-www-form-urlencoded"},
|
||||||
|
data = f"consumer_key={self.consumer_key}&consumer_secret={self.consumer_secret}&code={self.code}")
|
||||||
|
if response.status_code in [ 200, 201 ]:
|
||||||
|
print("Autenticado com sucesso!", flush=True)
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Erro ao autenticar na Tray: {response.status_code} | {response.text}", flush=True)
|
||||||
|
raise Exception(response.text)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error connecting to Tray: {e}", flush=True)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def refresh_token(self) -> str:
|
||||||
|
try:
|
||||||
|
print("\n")
|
||||||
|
print("Atualizando token de acesso da Tray...", flush=True)
|
||||||
|
response = requests.get(url = f"{self.url}/web_api/auth?access_token={self.get_token()}")
|
||||||
|
if response.status_code in [ 200, 201 ]:
|
||||||
|
print("Token atualizado com sucesso!", flush=True)
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Erro ao atualizar token na Tray: {response.status_code} | {response.text}", flush=True)
|
||||||
|
raise Exception(response.text)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error connecting to Tray: {e}", flush=True)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def get_token(self) -> str:
|
||||||
|
try:
|
||||||
|
chave = f"{self.system}_{self.service}_auth"
|
||||||
|
if self.redis_client.exists(chave):
|
||||||
|
auth = json.loads(self.redis_client.get(chave))
|
||||||
|
return auth['access_token']
|
||||||
|
|
||||||
|
# Busca o token e guarda no redis com cache de 2h e 55 minutos
|
||||||
|
token = self.auth()
|
||||||
|
self.redis_client.set(chave, json.dumps(token), ex=(2*3600))
|
||||||
|
return token['access_token']
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def get_pedido_completo(self, id_pedido: int) -> TrayOrderRootResponse:
|
||||||
|
try:
|
||||||
|
response = requests.get(url = f"{self.url}/web_api/orders/{id_pedido}/complete?access_token={self.get_token()}")
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"Pedido {id_pedido} obtido com sucesso!", flush=True)
|
||||||
|
return TrayOrderRootResponse(**response.json())
|
||||||
|
else:
|
||||||
|
print(f"Erro ao obter pedido {id_pedido} na Tray: {response.text}", flush=True)
|
||||||
|
raise Exception(response.text)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error connecting to Tray: {e}", flush=True)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def inserir_nfe_pedido(self, id_pedido: int, nfe: dict) -> None:
|
||||||
|
try:
|
||||||
|
response = requests.post(url = f"{self.url}/web_api/orders/{id_pedido}/invoices?access_token={self.get_token()}",
|
||||||
|
headers = {"Content-Type": "application/json"},
|
||||||
|
data = json.dumps(nfe))
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"NFE do pedido {id_pedido} atualizada com sucesso!", flush=True)
|
||||||
|
else:
|
||||||
|
print(f"Erro ao atualizar NFE do pedido {id_pedido} na Tray: {response.text}", flush=True)
|
||||||
|
raise Exception(response.text)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error connecting to Tray: {e}", flush=True)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def inserir_produto_tray(self, produto: dict) -> int:
|
||||||
|
try:
|
||||||
|
response = requests.post(url = f"{self.url}/web_api/products?access_token={self.get_token()}",
|
||||||
|
headers = {"Content-Type": "application/json"},
|
||||||
|
data = json.dumps(produto))
|
||||||
|
if response.status_code in [ 200, 201 ]:
|
||||||
|
print(f"Produto {produto['name']} inserido com sucesso!", flush=True)
|
||||||
|
return response.json()['id']
|
||||||
|
else:
|
||||||
|
print(f"Erro ao inserir produto {produto['name']} na Tray: {response.text}", flush=True)
|
||||||
|
raise Exception(response.text)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error connecting to Tray: {e}", flush=True)
|
||||||
|
raise e
|
||||||
124
src/appIC2Builder.py
Normal file
124
src/appIC2Builder.py
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
from datetime import datetime as dt
|
||||||
|
|
||||||
|
from typing import Tuple, Any
|
||||||
|
|
||||||
|
from exchangeModels import *
|
||||||
|
from Exchange2 import IC2Builder
|
||||||
|
|
||||||
|
class SystemConfig(BaseModel):
|
||||||
|
amqps : str
|
||||||
|
ic2_url : str
|
||||||
|
rabbitmq : str
|
||||||
|
system : str
|
||||||
|
service : str
|
||||||
|
client : str
|
||||||
|
enterprise_id : str
|
||||||
|
enterprise_name : str
|
||||||
|
servicekey : str
|
||||||
|
serviceid : str
|
||||||
|
tray_consumer_key : str
|
||||||
|
tray_consumer_secret: str
|
||||||
|
tray_code : str
|
||||||
|
tray_url : str
|
||||||
|
db_host : str
|
||||||
|
db_port : int
|
||||||
|
db_user : str
|
||||||
|
db_password : str
|
||||||
|
db_name : str
|
||||||
|
|
||||||
|
|
||||||
|
# AQUI PODEMOS ESTENDER OS REALMS TYPE DE ACORDO COM A APLICAÇÃO
|
||||||
|
# Dados cria o objeto que será passado para o IC2Builder na inicialização
|
||||||
|
class Dados(BaseModel):
|
||||||
|
service_date : str
|
||||||
|
startup_date : str
|
||||||
|
task_id : str
|
||||||
|
service : str
|
||||||
|
versionstr : str
|
||||||
|
origin : str
|
||||||
|
enterprise_id : str
|
||||||
|
enterprise_name : str
|
||||||
|
system : str
|
||||||
|
source_key : int | None = 0
|
||||||
|
destination_key : int | None = 0
|
||||||
|
|
||||||
|
# ESPECIALIZAÇÃO REALM OPERATIONAL - AQUI EXEMPLO PARA MIDDLEWARE DE VENDAS
|
||||||
|
class Operation(ICOperational):
|
||||||
|
source_key : int = 0
|
||||||
|
destination_key : int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class AppIC2Builder(IC2Builder):
|
||||||
|
def __init__(self, dados: object): # AQUI dados TEM INFORMACOES EM TEMPO DE INICIALIZAÇÃO
|
||||||
|
self.dados = dados
|
||||||
|
|
||||||
|
def _setLogs(self, message: tuple) -> InfoConnector: # AQUI message TEM INFORMACOES EM TEMPO DE EXECUÇÃO
|
||||||
|
msg, *extra = message
|
||||||
|
return InfoConnector(
|
||||||
|
realm = RealmType.LOGS,
|
||||||
|
messageIC = ICLogs(
|
||||||
|
log_date = dt.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
service_date = self.dados.service_date,
|
||||||
|
service = self.dados.service,
|
||||||
|
versionstr = self.dados.versionstr,
|
||||||
|
origin = self.dados.origin,
|
||||||
|
enterprise_id = self.dados.enterprise_id,
|
||||||
|
enterprise_name = self.dados.enterprise_name,
|
||||||
|
message = msg)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _setErrors(self, message: Tuple[str]) -> object: # AQUI message TEM INFORMACOES EM TEMPO DE EXECUÇÃO
|
||||||
|
return InfoConnector(
|
||||||
|
realm = RealmType.ERRORS,
|
||||||
|
messageIC = ICErrors(
|
||||||
|
log_date = dt.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
service_date = self.dados.service_date,
|
||||||
|
service = self.dados.service,
|
||||||
|
versionstr = self.dados.versionstr,
|
||||||
|
origin = self.dados.origin,
|
||||||
|
enterprise_id = self.dados.enterprise_id,
|
||||||
|
enterprise_name = self.dados.enterprise_name,
|
||||||
|
message = message[0])
|
||||||
|
)
|
||||||
|
|
||||||
|
def _setOperational(self, message:Tuple[str,str,OpStatus]) -> object: # AQUI message TEM INFORMACOES EM TEMPO DE EXECUÇÃO
|
||||||
|
return InfoConnector(
|
||||||
|
realm = RealmType.OPERATIONAL,
|
||||||
|
messageIC = Operation(
|
||||||
|
log_date = dt.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
service_date = self.dados.service_date, # DATA DE INICIO DO TASK/SERVIÇO
|
||||||
|
service_name = self.dados.service, # NOME DO SERVIÇO
|
||||||
|
task_id = self.dados.task_id, # ID DO TASK
|
||||||
|
startup_date = self.dados.startup_date, # DATA DE INICIO DO TASK/SERVIÇO
|
||||||
|
versionstr = self.dados.versionstr,
|
||||||
|
origin = self.dados.origin,
|
||||||
|
enterprise_id = self.dados.enterprise_id,
|
||||||
|
enterprise_name = self.dados.enterprise_name,
|
||||||
|
source_key = self.dados.source_key,
|
||||||
|
destination_key = self.dados.destination_key,
|
||||||
|
step_status = message[0], # step_status
|
||||||
|
message = message[1], # msg
|
||||||
|
status = message[2] # status
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _setStatus(self, message=Tuple[(int, float, float, float, float)]) -> object: # AQUI message TEM INFORMACOES EM TEMPO DE EXECUÇÃO
|
||||||
|
return InfoConnector(
|
||||||
|
realm = RealmType.STATUS,
|
||||||
|
messageIC = ICStatus(
|
||||||
|
service = self.dados.service,
|
||||||
|
versionstr = self.dados.versionstr,
|
||||||
|
system_name = self.dados.enterprise_name,
|
||||||
|
startup_date = self.dados.startup_date,
|
||||||
|
access_date = dt.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
origin = self.dados.origin,
|
||||||
|
sid = self.dados.enterprise_id,
|
||||||
|
task_count = message[0],
|
||||||
|
cpu = message[1],
|
||||||
|
memory = message[2],
|
||||||
|
disk = message[3],
|
||||||
|
network = message[4])
|
||||||
|
)
|
||||||
|
|
||||||
|
def _setMetrics(self, message=Tuple[()]) -> object: # AQUI message TEM INFORMACOES EM TEMPO DE EXECUÇÃO
|
||||||
|
pass
|
||||||
655
src/controller.py
Normal file
655
src/controller.py
Normal file
|
|
@ -0,0 +1,655 @@
|
||||||
|
from datetime import datetime as dt
|
||||||
|
# pyrefly: ignore [missing-import]
|
||||||
|
from pydantic import ValidationError, TypeAdapter
|
||||||
|
from json.decoder import JSONDecodeError
|
||||||
|
|
||||||
|
from Exchange2 import QueueExchange2, LoggerExchange2
|
||||||
|
from exchangeModels import *
|
||||||
|
from HostMetrics import *
|
||||||
|
from appIC2Builder import *
|
||||||
|
|
||||||
|
from services.cliente import ClienteService
|
||||||
|
from services.nfe import NfeService
|
||||||
|
from services.titulo_receber import TituloReceberService
|
||||||
|
|
||||||
|
from db.banco import Banco
|
||||||
|
|
||||||
|
from models.recebimento import Recebimento, RecebimentoPlugnotas
|
||||||
|
from models.sg_tray_models.services_model import TrayOrderRootResponse
|
||||||
|
|
||||||
|
from api.tray import ApiTray
|
||||||
|
|
||||||
|
from send_rpc import SendRPCService
|
||||||
|
from send_queue import SendQueueService
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import base64
|
||||||
|
import redis
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Versão do script e histórico básico
|
||||||
|
version = "1"
|
||||||
|
versionstr = f"v{version}.0.0 - BALT - 24/04/2026 - VERSÃO INICIAL"
|
||||||
|
|
||||||
|
system = "innovar_amr_tray"
|
||||||
|
service = "controller"
|
||||||
|
tenant = "innovar"
|
||||||
|
startup_date = dt.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
TaskCount = 0
|
||||||
|
metrics = HostMetrics()
|
||||||
|
stats = metrics.get_metrics()
|
||||||
|
# Obtém o nome do ambiente/container
|
||||||
|
try:
|
||||||
|
with open("/app/hostname") as f:
|
||||||
|
origin= f.read().strip()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Montar o arquivo hostname no compose: {e}", flush=True)
|
||||||
|
time.sleep(5)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
############################################################## < Inicio logica > ##############################################################
|
||||||
|
|
||||||
|
def buscar_dados_tray(id_pedido_tray) -> TrayOrderRootResponse | None:
|
||||||
|
step_status = "BUSCANDO PEDIDO NO TRAY"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"INICIADO",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
api_tray = ApiTray(system_config=system_config, redis_client=rd)
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao inicializar ApiTray: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
pedido = api_tray.get_pedido_completo(id_pedido_tray)
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, f"Pedido encontrado: {pedido}", OpStatus.OK))
|
||||||
|
return pedido
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao buscar pedido no Tray: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def criar_cliente(pedido: TrayOrderRootResponse, banco: Banco) -> int | None:
|
||||||
|
step_status = "CRIANDO CLIENTE NO SIENGE"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"INICIADO",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
cliente_service = ClienteService(config=system_config, redis_client=rd)
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao inicializar ClienteService: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
logger.logMsgError(f"Erro ao inicializar ClienteService: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"BUSCANDO CONFIGURAÇÃO DO CLIENTE",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
config_tenant = banco.get_config_cliente(tenant=tenant)
|
||||||
|
if not config_tenant:
|
||||||
|
message = f"Configuração do cliente '{tenant}' não encontrada no banco de dados."
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao obter configuração do cliente: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"MAPPING DOS DADOS",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
payload = cliente_service._mapper_tray_to_sienge_cliente(pedido, config_tenant)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao mapear cliente: {e}",flush=True)
|
||||||
|
message = f"Erro ao mapear dados do cliente: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"BUSCANDO CREDENCIAIS DO INTEGRADOR",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
integrador = banco.get_credenciais_servicos_integradores(integrador="cliente")
|
||||||
|
if not integrador:
|
||||||
|
message = f"Credenciais para integrador 'cliente' não encontradas no banco de dados."
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao obter credenciais do integrador: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"ENVIANDO DADOS PARA FILA DE CLIENTE (RPC)",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
rpc = SendRPCService(
|
||||||
|
system = integrador.system,
|
||||||
|
service = integrador.service,
|
||||||
|
amqps = integrador.amqps,
|
||||||
|
origin = origin,
|
||||||
|
serviceid = integrador.serviceid,
|
||||||
|
servicekey = integrador.servicekey
|
||||||
|
)
|
||||||
|
print(payload, flush=True)
|
||||||
|
response = rpc.send_rpc(payload=payload)
|
||||||
|
|
||||||
|
payload = response.get('payload')
|
||||||
|
hmac = response.get('hmac')
|
||||||
|
if not payload or not hmac:
|
||||||
|
message = f"Resposta RPC inválida: {response}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
response_data = rpc.get_payload(hmac, payload)
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, f"Cliente criado: {response_data.get('customer_id')}", OpStatus.OK))
|
||||||
|
return response_data.get('customer_id')
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao enviar dados para fila de cliente: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def criar_titulo(pedido: TrayOrderRootResponse, banco: Banco, chave_nfe: str, numero_nfe: str) -> int | None:
|
||||||
|
step_status = "CRIANDO TÍTULO A RECEBER NO SIENGE"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"INICIADO",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
titulo_service = TituloReceberService(config=system_config)
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao inicializar TituloReceberService: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"BUSCANDO CONFIGURAÇÃO DO CLIENTE",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
config_tenant = banco.get_config_cliente(tenant=tenant)
|
||||||
|
if not config_tenant:
|
||||||
|
message = f"Configuração do cliente '{tenant}' não encontrada no banco de dados."
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao obter configuração do cliente: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"BUSCANDO APROPRIAÇÃO FINANCEIRA",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
apropriacao = banco.get_lista_apropriacao_financeira(tenant=tenant)
|
||||||
|
if not apropriacao:
|
||||||
|
message = f"Apropriação financeira para tenant '{tenant}' não encontrada no banco de dados."
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao obter apropriação financeira: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"BUSCANDO PEDIDO",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
pedido_db = banco.get_pedido(id_pedido_tray=pedido.Order.id)
|
||||||
|
if not pedido_db:
|
||||||
|
message = f"Pedido '{pedido.id}' não encontrado no banco de dados."
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao obter pedido: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"MAPPING DOS DADOS",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
payload = titulo_service.map_tray_order_to_bill_data(pedido, config_tenant, apropriacao, pedido_db, chave_nfe, numero_nfe)
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao mapear dados: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"BUSCANDO CREDENCIAIS DO INTEGRADOR",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
integrador = banco.get_credenciais_servicos_integradores(integrador="titulo-cr")
|
||||||
|
if not integrador:
|
||||||
|
message = f"Credenciais para integrador 'titulo-cr' não encontradas no banco de dados."
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao obter credenciais do integrador: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"ENVIANDO DADOS PARA FILA DE TÍTULO A RECEBER (RPC)",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
rpc = SendRPCService(
|
||||||
|
system = integrador.system,
|
||||||
|
service = integrador.service,
|
||||||
|
amqps = integrador.amqps,
|
||||||
|
origin = origin,
|
||||||
|
serviceid = integrador.serviceid,
|
||||||
|
servicekey = integrador.servicekey
|
||||||
|
)
|
||||||
|
response = rpc.send_rpc(payload=payload.model_dump(exclude_none=True))
|
||||||
|
|
||||||
|
payload = response.get('payload')
|
||||||
|
hmac = response.get('hmac')
|
||||||
|
if not payload or not hmac:
|
||||||
|
message = f"Resposta RPC inválida: {response}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
response_data = rpc.get_payload(hmac, payload)
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, f"Título a receber criado: {response_data.get('id_titulo')}", OpStatus.OK))
|
||||||
|
return response_data.get('id_titulo')
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao enviar dados para título a receber: {e}", flush=True)
|
||||||
|
message = f"Erro ao enviar dados para fila de título a receber: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def criar_nfe(pedido: TrayOrderRootResponse, banco: Banco) -> bool:
|
||||||
|
step_status = "NFe - CRIACAO"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"INICIADO",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
nfe = NfeService(system_config, rd)
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao inicializar NFeService: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"BUSCANDO CONFIGURAÇÃO DO CLIENTE",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
config_tenant = banco.get_config_cliente(tenant=tenant)
|
||||||
|
if not config_tenant:
|
||||||
|
message = f"Configuração do cliente '{tenant}' não encontrada no banco de dados."
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao obter configuração do cliente: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"MAPPING DOS DADOS",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
payload = nfe._mapper_pedido_tray_to_nfe(pedido, config_tenant)
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao mapear dados: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"BUSCANDO CREDENCIAIS DO INTEGRADOR",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
integrador = banco.get_credenciais_servicos_integradores(integrador="nfe")
|
||||||
|
if not integrador:
|
||||||
|
message = f"Credenciais para integrador 'nfe' não encontradas no banco de dados."
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao obter credenciais do integrador: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"ENVIANDO DADOS PARA FILA DE NFE (QUEUE)",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
queue = SendQueueService(
|
||||||
|
system = integrador.system,
|
||||||
|
service = integrador.service,
|
||||||
|
amqps = integrador.amqps,
|
||||||
|
origin = origin,
|
||||||
|
serviceid = integrador.serviceid,
|
||||||
|
servicekey = integrador.servicekey
|
||||||
|
)
|
||||||
|
queue.send_queue(payload=payload.model_dump())
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Dados enviados para fila de NFe", OpStatus.OK))
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao enviar dados para fila de NFe: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def atualizar_nfe_tray(id_pedido_tray: str, xml: str, banco: Banco) -> bool:
|
||||||
|
step_status = "NFe - ATUALIZACAO TRAY"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"Localizando dados no XML",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
ns = {"nfe": "http://www.portalfiscal.inf.br/nfe"}
|
||||||
|
root = ET.fromstring(xml)
|
||||||
|
|
||||||
|
chave = root.find(".//nfe:chNFe", ns)
|
||||||
|
data_emissao = root.find(".//nfe:dhEmi", ns)
|
||||||
|
serie = root.find(".//nfe:serie", ns)
|
||||||
|
value = root.find(".//nfe:vNF", ns)
|
||||||
|
numero = root.find(".//nfe:nNF", ns)
|
||||||
|
|
||||||
|
chave = chave.text if chave is not None else None
|
||||||
|
data_emissao = data_emissao.text if data_emissao is not None else None
|
||||||
|
serie = serie.text if serie is not None else None
|
||||||
|
value = value.text if value is not None else None
|
||||||
|
numero = numero.text if numero is not None else None
|
||||||
|
|
||||||
|
if not all([chave, data_emissao, serie, value, numero]):
|
||||||
|
message = "Dados necessários não encontrados no XML"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
itens = root.findall(".//nfe:det", ns)
|
||||||
|
produtos = []
|
||||||
|
for item in itens:
|
||||||
|
cProd = item.find(".//nfe:cProd", ns)
|
||||||
|
xProd = item.find(".//nfe:xProd", ns)
|
||||||
|
cfop = item.find(".//nfe:CFOP", ns)
|
||||||
|
produtos.append({
|
||||||
|
"product_id" : cProd.text if cProd is not None else None,
|
||||||
|
"variation_id": xProd.text if xProd is not None else None,
|
||||||
|
"cfop" : cfop.text if cfop is not None else None
|
||||||
|
})
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"issue_date" : data_emissao[:10],
|
||||||
|
"number" : numero,
|
||||||
|
"serie" : serie,
|
||||||
|
"value" : value,
|
||||||
|
"key" : chave,
|
||||||
|
"link" : None,
|
||||||
|
"xml_danfe" : xml,
|
||||||
|
"ProductCfop": produtos
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao extrair dados do XML da NFe: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
step_status = "NFe - ATUALIZACAO TRAY"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"Enviando dados para atualização do pedido no Tray",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
api_tray = ApiTray(system_config=system_config, redis_client=rd)
|
||||||
|
api_tray.inserir_nfe_pedido(id_pedido=id_pedido_tray, nfe=payload)
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Dados enviados para atualização do pedido no Tray", OpStatus.OK))
|
||||||
|
# return True
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao enviar dados para atualização do pedido no Tray: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
# return False
|
||||||
|
|
||||||
|
step_status = "ATUALIZANDO NFe NO BANCO"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status,"INICIADO",OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
banco.update_pedido_nfe(id_pedido_tray = id_pedido_tray, status="Faturado", nfe_gerada = True,nfe_numero=numero,nfe_serie=serie)
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "NFe atualizada no banco de dados", OpStatus.OK))
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao atualizar NFe no banco de dados: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return False
|
||||||
|
|
||||||
|
############################################################## < Fim logica > ##############################################################
|
||||||
|
|
||||||
|
def sendStatus(logger: LoggerExchange2):
|
||||||
|
global TaskCount, stats
|
||||||
|
TaskCount += 1
|
||||||
|
stats = metrics.get_metrics()
|
||||||
|
cpu_p, cores, load_1, load_5, load_15, m_total, m_used, m_free, m_pct, net_up, net_down, net_total, disk_usage = stats
|
||||||
|
logger.logIC2(realm = RealmType.STATUS, msg = (TaskCount, cpu_p, m_pct, disk_usage, net_total))
|
||||||
|
|
||||||
|
def tratar_recebimento_webhook_tray(recebimento: Recebimento):
|
||||||
|
logger.logMsg(f"Tratando recebimento do webhook Tray: {recebimento}")
|
||||||
|
if recebimento.data.scope_name != "order":
|
||||||
|
logger.logMsg(f"Escopo desconhecido: {recebimento.data.scope_name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.ic2_builder.dados.task_id = recebimento.data.scope_id
|
||||||
|
|
||||||
|
dados_tray = buscar_dados_tray(recebimento.data.scope_id)
|
||||||
|
if not dados_tray:
|
||||||
|
logger.logMsgError("Erro ao buscar dados do pedido no Tray. Verifique os logs para mais detalhes.")
|
||||||
|
return
|
||||||
|
|
||||||
|
step_status = "INICIANDO BANCO DE DADOS"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Iniciando conexão com o banco de dados", OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
banco = Banco(database = system_config.db_name,
|
||||||
|
user = system_config.db_user,
|
||||||
|
password = system_config.db_password,
|
||||||
|
host = system_config.db_host,
|
||||||
|
port = system_config.db_port)
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Banco de dados inicializado com sucesso", OpStatus.OK))
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao conectar ao banco de dados: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return
|
||||||
|
|
||||||
|
step_status = "INSERINDO PEDIDO NO BANCO CASO NÃO EXISTA"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Iniciando inserção do pedido no banco de dados caso não exista", OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
banco.upsert_pedido(id_pedido_tray=recebimento.data.scope_id, status=recebimento.data.act, tenant=tenant)
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Pedido inserido no banco de dados com sucesso", OpStatus.OK))
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao inserir pedido no banco de dados: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return
|
||||||
|
|
||||||
|
match recebimento.data.act:
|
||||||
|
case "insert":
|
||||||
|
cliente_id = criar_cliente(dados_tray, banco)
|
||||||
|
if not cliente_id:
|
||||||
|
return
|
||||||
|
step_status = "ATUALIZANDO CLIENTE NO PEDIDO (DB)"
|
||||||
|
logger.logIC2(RealmType.OPERATIONAL,msg=(step_status, "Iniciando atualização do cliente no pedido", OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
banco.update_pedido_cliente(id_pedido_tray=recebimento.data.scope_id, codigo_cliente=cliente_id)
|
||||||
|
logger.logIC2(RealmType.OPERATIONAL,msg=(step_status, "Cliente atualizado no pedido com sucesso", OpStatus.OK))
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao atualizar cliente no pedido: {e}"
|
||||||
|
logger.logIC2(RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return
|
||||||
|
|
||||||
|
case "update":
|
||||||
|
if dados_tray.Order.status.upper() != "A ENVIAR":
|
||||||
|
logger.logMsgError(f"Status do pedido diferente do esperado {dados_tray.Order.status.upper()}")
|
||||||
|
|
||||||
|
nfe = criar_nfe(dados_tray, banco)
|
||||||
|
if not nfe:
|
||||||
|
return
|
||||||
|
|
||||||
|
step_status = "ATUALIZANDO ENVIADO PLUGNOTAS NO PEDIDO (DB)"
|
||||||
|
logger.logIC2(RealmType.OPERATIONAL,msg=(step_status, "Atualizando pedido no banco", OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
banco.update_pedido_enviado_plugnotas(id_pedido_tray=recebimento.data.scope_id, enviado_plugnotas=True)
|
||||||
|
logger.logIC2(RealmType.OPERATIONAL,msg=(step_status, "enviado_plugnotas atualizado no banco", OpStatus.OK))
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao atualizar enviado_plugnotas no pedido: {e}"
|
||||||
|
logger.logIC2(RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return
|
||||||
|
case _:
|
||||||
|
logger.logMsg(f"Ação desconhecida: {recebimento.data.act}")
|
||||||
|
return
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def tratar_webhook_plugnotas(recebimento: RecebimentoPlugnotas):
|
||||||
|
|
||||||
|
print(f"WebHook recebido do PlugNotas: {recebimento}", flush=True)
|
||||||
|
if recebimento.status.upper() != "CONCLUIDO":
|
||||||
|
logger.logMsgError(f"Status do webhook do PlugNotas diferente do esperado: {recebimento.status}")
|
||||||
|
return
|
||||||
|
|
||||||
|
id_pedido_tray = recebimento.idIntegracao.split("_")[1]
|
||||||
|
|
||||||
|
dados_tray = buscar_dados_tray(id_pedido_tray)
|
||||||
|
if not dados_tray:
|
||||||
|
logger.logMsgError(f"Erro ao buscar dados do pedido no Tray: {id_pedido_tray}")
|
||||||
|
return
|
||||||
|
|
||||||
|
step_status = "INICIANDO BANCO DE DADOS"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Iniciando conexão com o banco de dados", OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
banco = Banco(database = system_config.db_name,
|
||||||
|
user = system_config.db_user,
|
||||||
|
password = system_config.db_password,
|
||||||
|
host = system_config.db_host,
|
||||||
|
port = system_config.db_port)
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Banco de dados inicializado com sucesso", OpStatus.OK))
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao conectar ao banco de dados: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return
|
||||||
|
|
||||||
|
step_status = "BUSCANDO CHAVE DE ACESSO DA NFE"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Buscando chave de acesso da NFe no banco de dados", OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
xml = base64.b64decode(recebimento.xml_base64).decode()
|
||||||
|
ns = {"nfe": "http://www.portalfiscal.inf.br/nfe"}
|
||||||
|
root = ET.fromstring(xml)
|
||||||
|
chave_nfe = root.find(".//nfe:chNFe", ns)
|
||||||
|
numero = root.find(".//nfe:nNF", ns)
|
||||||
|
if chave_nfe is not None and numero is not None:
|
||||||
|
chave_nfe = chave_nfe.text
|
||||||
|
numero_nfe = numero.text
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, f"Chave de acesso da NFe encontrada: {chave_nfe}", OpStatus.ACTIVE))
|
||||||
|
else:
|
||||||
|
message = "Chave de acesso da NFe não encontrada no XML"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
message = f"Erro ao buscar chave de acesso da NFe: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return
|
||||||
|
|
||||||
|
titulo = criar_titulo(dados_tray, banco, chave_nfe, numero_nfe)
|
||||||
|
if not titulo:
|
||||||
|
return
|
||||||
|
|
||||||
|
step_status = "ATUALIZANDO TITULO NO PEDIDO NO BANCO"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Iniciando atualização do titulo no pedido no banco", OpStatus.ACTIVE))
|
||||||
|
try:
|
||||||
|
banco.update_pedido_titulo(id_pedido_tray=id_pedido_tray, titulo_gerado=titulo)
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Titulo atualizado no pedido no banco com sucesso", OpStatus.OK))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar titulo no pedido no banco: {e}", flush=True)
|
||||||
|
message = f"Erro ao atualizar titulo no pedido no banco: {e}"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, message, OpStatus.ERROR))
|
||||||
|
return
|
||||||
|
|
||||||
|
step_status = "ATUALIZANDO NFE NO TRAY"
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "Iniciando atualização da NFe no Tray", OpStatus.ACTIVE))
|
||||||
|
if not atualizar_nfe_tray(id_pedido_tray, xml):
|
||||||
|
logger.logMsgError("Erro ao atualizar NFe no Tray. Verifique os logs para mais detalhes.")
|
||||||
|
return
|
||||||
|
logger.logIC2(realm=RealmType.OPERATIONAL,msg=(step_status, "NFe atualizada no Tray com sucesso", OpStatus.OK))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def callback(ch, method, properties, body):
|
||||||
|
# Controlador de mensagens do RabbitMQ: decodifica a mensagem e inicia o processo de clonagem.
|
||||||
|
global logger
|
||||||
|
sendStatus(logger)
|
||||||
|
logger.logMsg(f"Mensagem recebida: {body}")
|
||||||
|
# Confirma o recebimento da mensagem (ACK) para remover da fila
|
||||||
|
ch.basic_ack(delivery_tag=method.delivery_tag)
|
||||||
|
|
||||||
|
dado = json.loads(body)
|
||||||
|
hmac = dado['hmac']
|
||||||
|
payload = dado['payload']
|
||||||
|
|
||||||
|
if not hmac or not payload:
|
||||||
|
logger.logMsg("HMAC ou Payload inválido")
|
||||||
|
return
|
||||||
|
|
||||||
|
exchange.setCrypto(serviceid=system_config.serviceid, servicekey=system_config.servicekey)
|
||||||
|
|
||||||
|
if not exchange.getPayload(hmac, payload):
|
||||||
|
logger.logMsg("Falha ao descifrar payload")
|
||||||
|
return
|
||||||
|
|
||||||
|
payload_dict = json.loads(exchange.payload)
|
||||||
|
if not payload_dict:
|
||||||
|
logger.logMsg("Payload inválido")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Verifica qual modelo de recebimento é compativel -
|
||||||
|
PayloadModel = Recebimento | RecebimentoPlugnotas
|
||||||
|
adapter = TypeAdapter(PayloadModel)
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.logMsg(f"Validando payload recebido: {payload_dict}")
|
||||||
|
recebimento = adapter.validate_python(payload_dict)
|
||||||
|
if isinstance(recebimento, Recebimento):
|
||||||
|
logger.logMsg("Payload validado como Recebimento do webhook Tray")
|
||||||
|
tratar_recebimento_webhook_tray(recebimento)
|
||||||
|
elif isinstance(recebimento, RecebimentoPlugnotas):
|
||||||
|
logger.logMsg("Payload validado como Recebimento do webhook PlugNotas")
|
||||||
|
tratar_webhook_plugnotas(recebimento)
|
||||||
|
else:
|
||||||
|
logger.logMsgError("Modelo de recebimento fora do padrão")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.logMsgError(f"Erro ao converter recebimento \n {recebimento} \n {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --- Lógica de Inicialização do Script ---
|
||||||
|
|
||||||
|
# CONFIGURAÇÃO SERVIÇO REDIS
|
||||||
|
try:
|
||||||
|
rd = redis.Redis(host='redis', port=6379, decode_responses=True)
|
||||||
|
key = f"{system}-system_config-{tenant}"
|
||||||
|
if not rd.exists(key):
|
||||||
|
msg = f"Key '{key}' not found in Redis."
|
||||||
|
print(msg, flush=True)
|
||||||
|
time.sleep(5)
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error connecting to Redis: A {e}", flush=True)
|
||||||
|
time.sleep(5)
|
||||||
|
sys.exit(0)
|
||||||
|
# VALIDA JSON
|
||||||
|
try:
|
||||||
|
configRaw = rd.get(key)
|
||||||
|
configJS = json.loads(configRaw)
|
||||||
|
except JSONDecodeError as e:
|
||||||
|
print(f"Error connecting to Redis: B {e}", flush=True)
|
||||||
|
time.sleep(5)
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error REDIS GET KEY {key}: {e}", flush=True)
|
||||||
|
time.sleep(5)
|
||||||
|
sys.exit(0)
|
||||||
|
# VALIDA QUALIDADE DO JSON
|
||||||
|
try:
|
||||||
|
system_config = SystemConfig(**configJS)
|
||||||
|
except ValidationError as e:
|
||||||
|
print(f"Error connecting to Redis: C {e}", flush=True)
|
||||||
|
time.sleep(5)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Estruturas de dados para logging e telemetria
|
||||||
|
dados = Dados(
|
||||||
|
service_date = dt.now().strftime("%Y-%m-%d %H:%M:%S"), # INICIO DO TRABALHO - FICA FIXO ATÉ FINAL DO TRABALHO
|
||||||
|
startup_date = startup_date,
|
||||||
|
task_id = "00000000", #secrets.token_hex(8).upper(),
|
||||||
|
service = service,
|
||||||
|
versionstr = versionstr,
|
||||||
|
origin = origin,
|
||||||
|
enterprise_id = system_config.enterprise_id,
|
||||||
|
enterprise_name = system_config.enterprise_name,
|
||||||
|
system = system
|
||||||
|
)
|
||||||
|
logger = LoggerExchange2(
|
||||||
|
ic2_builder = AppIC2Builder(dados),
|
||||||
|
amqps = system_config.ic2_url,
|
||||||
|
system = system_config.system,
|
||||||
|
service = service,
|
||||||
|
version = versionstr,
|
||||||
|
origin = origin,
|
||||||
|
client = tenant,
|
||||||
|
logType = "logs",
|
||||||
|
logLocal = True
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
exchange = QueueExchange2(
|
||||||
|
amqps = system_config.amqps,
|
||||||
|
origin = origin,
|
||||||
|
system = system,
|
||||||
|
service = service,
|
||||||
|
version = versionstr,
|
||||||
|
# client = tenant,
|
||||||
|
callback= callback)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro conectando a fila: {e}", flush=True)
|
||||||
|
time.sleep(5)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Inicialização operacional
|
||||||
|
logger.logMsg(f"{system.upper()} - SERVIÇO DE RESERVAS v{version}")
|
||||||
|
|
||||||
|
|
||||||
|
# Inicia o consumo perpétuo da fila
|
||||||
|
exchange.start_consuming()
|
||||||
5
src/create_tables.py
Normal file
5
src/create_tables.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
from db.banco import Banco
|
||||||
|
|
||||||
|
db = Banco(database="innovar_amr_tray_controller",port=5432, user="softgo", password="[PASSWORD]", host="[IP_ADDRESS]")
|
||||||
|
|
||||||
|
db.create_tables()
|
||||||
284
src/db/banco.py
Normal file
284
src/db/banco.py
Normal file
|
|
@ -0,0 +1,284 @@
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from sqlalchemy import Column, DateTime, Integer, Numeric, String, Text, Boolean, create_engine
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
class CredenciaisServicosIntegradores(Base):
|
||||||
|
__tablename__ = 'credenciais_servicos_integradores'
|
||||||
|
__table_args__ = {'schema': 'controller'}
|
||||||
|
|
||||||
|
id_credenciais_servicos_integradores = Column(Integer, primary_key=True)
|
||||||
|
integrador = Column(String, nullable=False)
|
||||||
|
amqps = Column(String, nullable=False)
|
||||||
|
service = Column(String, nullable=False)
|
||||||
|
system = Column(String, nullable=False)
|
||||||
|
serviceid = Column(String, nullable=False)
|
||||||
|
servicekey = Column(String, nullable=False)
|
||||||
|
|
||||||
|
class ConfigTenant(Base):
|
||||||
|
__tablename__ = 'config_clientes'
|
||||||
|
__table_args__ = {'schema': 'controller'}
|
||||||
|
|
||||||
|
id_config_cliente = Column(Integer, primary_key=True)
|
||||||
|
tenant = Column(String, nullable=False)
|
||||||
|
cpf_cnpj = Column(String, nullable=False)
|
||||||
|
empresa_integracao = Column(String, nullable=False)
|
||||||
|
indexador_integracao = Column(String, nullable=False)
|
||||||
|
inscricao_estadual = Column(String)
|
||||||
|
usuario_sienge = Column(String, nullable=False)
|
||||||
|
token_sienge = Column(String, nullable=False)
|
||||||
|
oauth = Column(Boolean, nullable=False)
|
||||||
|
|
||||||
|
class ApropriacaoFinanceira(Base):
|
||||||
|
__tablename__ = 'apropriacao_financeira'
|
||||||
|
__table_args__ = {'schema': 'controller'}
|
||||||
|
|
||||||
|
id_apropriacao_financeira = Column(Integer, primary_key=True)
|
||||||
|
tenant = Column(String, nullable=False)
|
||||||
|
centro_custo = Column(Integer, nullable=False)
|
||||||
|
plano_financeiro = Column(String, nullable=False)
|
||||||
|
percentual_apropriacao = Column(Numeric, nullable=False)
|
||||||
|
|
||||||
|
class Pedido(Base):
|
||||||
|
__tablename__ = 'pedido'
|
||||||
|
__table_args__ = {'schema': 'controller'}
|
||||||
|
|
||||||
|
id_pedido_tray = Column(Integer, primary_key=True)
|
||||||
|
tenant = Column(String, nullable=False)
|
||||||
|
status = Column(String, nullable=False)
|
||||||
|
codigo_cliente = Column(String)
|
||||||
|
enviado_plugnotas = Column(Boolean, nullable=False)
|
||||||
|
nfe_gerada = Column(Boolean, nullable=False)
|
||||||
|
numero_nfe = Column(String)
|
||||||
|
serie_nfe = Column(String)
|
||||||
|
titulo_gerado = Column(Integer)
|
||||||
|
data_criacao = Column(DateTime)
|
||||||
|
data_atualizacao = Column(DateTime)
|
||||||
|
|
||||||
|
class Banco:
|
||||||
|
def __init__(self, host, port, user, password, database):
|
||||||
|
try:
|
||||||
|
self.engine = create_engine(
|
||||||
|
f"postgresql+psycopg2://{user}:{password}@{host}:{port}/{database}",
|
||||||
|
echo = False,
|
||||||
|
future = True,
|
||||||
|
pool_pre_ping = True,
|
||||||
|
pool_recycle = 1800,
|
||||||
|
logging_name = "TrayController",
|
||||||
|
)
|
||||||
|
self.Session = sessionmaker(bind=self.engine)
|
||||||
|
self.session = self.Session()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao conectar ao banco de dados: {e}")
|
||||||
|
raise e
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
print(f"Erro ao conectar ao banco de dados: {e}")
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def create_tables(self):
|
||||||
|
try:
|
||||||
|
Base.metadata.create_all(self.engine)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao criar tabelas: {e}")
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
print(f"Erro ao criar tabelas: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
# MÉTODOS PARA GERENCIAMENTO DE PEDIDOS - AQUI PODEMOS IMPLEMENTAR MÉTODOS PARA INSERIR, ATUALIZAR E CONSULTAR PEDIDOS NO BANCO DE DADOS
|
||||||
|
|
||||||
|
def get_pedido(self, id_pedido_tray):
|
||||||
|
try:
|
||||||
|
pedido = self.session.query(Pedido).filter(Pedido.id_pedido_tray == id_pedido_tray).first()
|
||||||
|
return pedido
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao buscar pedido: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def get_all_pedidos(self):
|
||||||
|
try:
|
||||||
|
pedidos = self.session.query(Pedido).all()
|
||||||
|
return pedidos
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao buscar pedidos: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def upsert_pedido(self, id_pedido_tray, status, tenant):
|
||||||
|
try:
|
||||||
|
pedido = self.session.query(Pedido).filter(Pedido.id_pedido_tray == id_pedido_tray).first()
|
||||||
|
if not pedido:
|
||||||
|
pedido = Pedido(
|
||||||
|
id_pedido_tray = id_pedido_tray,
|
||||||
|
status = status,
|
||||||
|
tenant = tenant,
|
||||||
|
enviado_plugnotas = False,
|
||||||
|
nfe_gerada = False,
|
||||||
|
data_criacao = datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
self.session.add(pedido)
|
||||||
|
else:
|
||||||
|
pedido.status = status
|
||||||
|
pedido.data_atualizacao = datetime.now(timezone.utc)
|
||||||
|
self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao inserir pedido: {e}", flush=True)
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def update_pedido_cliente(self, id_pedido_tray, codigo_cliente):
|
||||||
|
try:
|
||||||
|
pedido = self.session.query(Pedido).filter(Pedido.id_pedido_tray == id_pedido_tray).first()
|
||||||
|
if pedido:
|
||||||
|
print(f"Atualizando pedido {id_pedido_tray} com cliente {codigo_cliente}", flush=True)
|
||||||
|
pedido.codigo_cliente = str(codigo_cliente)
|
||||||
|
pedido.data_atualizacao = datetime.now(timezone.utc)
|
||||||
|
self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar pedido: {e}", flush=True)
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def update_pedido_enviado_plugnotas(self, id_pedido_tray, enviado_plugnotas):
|
||||||
|
try:
|
||||||
|
pedido = self.session.query(Pedido).filter(Pedido.id_pedido_tray == id_pedido_tray).first()
|
||||||
|
if pedido:
|
||||||
|
pedido.enviado_plugnotas = enviado_plugnotas
|
||||||
|
pedido.data_atualizacao = datetime.now(timezone.utc)
|
||||||
|
self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar pedido: {e}", flush=True)
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def update_pedido_nfe(self, id_pedido_tray, status, nfe_gerada, nfe_numero, nfe_serie):
|
||||||
|
try:
|
||||||
|
pedido = self.session.query(Pedido).filter(Pedido.id_pedido_tray == id_pedido_tray).first()
|
||||||
|
if pedido:
|
||||||
|
pedido.status = status
|
||||||
|
pedido.nfe_gerada = nfe_gerada
|
||||||
|
pedido.nfe_numero = nfe_numero
|
||||||
|
pedido.nfe_serie = nfe_serie
|
||||||
|
pedido.data_atualizacao = datetime.now(timezone.utc)
|
||||||
|
self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar pedido: {e}", flush=True )
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def update_pedido_titulo(self, id_pedido_tray, titulo_gerado):
|
||||||
|
try:
|
||||||
|
pedido = self.session.query(Pedido).filter(Pedido.id_pedido_tray == id_pedido_tray).first()
|
||||||
|
if pedido:
|
||||||
|
pedido.titulo_gerado = titulo_gerado
|
||||||
|
pedido.data_atualizacao = datetime.now(timezone.utc)
|
||||||
|
self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar pedido: {e}", flush=True)
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
# MÉTODOS PARA GERENCIAMENTO DE CONFIGURAÇÕES - AQUI PODEMOS IMPLEMENTAR MÉTODOS PARA INSERIR, ATUALIZAR E CONSULTAR CONFIGURAÇÕES DE CLIENTES NO BANCO DE DADOS
|
||||||
|
def get_config_cliente(self, tenant):
|
||||||
|
try:
|
||||||
|
config = self.session.query(ConfigTenant).filter(ConfigTenant.tenant == tenant).first()
|
||||||
|
return config
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao buscar configuração do cliente: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def insert_config_cliente(self, tenant, cpf_cnpj, empresa_integracao, indexador_integracao, inscricao_estadual, usuario_sienge, token_sienge, oauth):
|
||||||
|
try:
|
||||||
|
config = ConfigTenant(
|
||||||
|
tenant = tenant,
|
||||||
|
cpf_cnpj = cpf_cnpj,
|
||||||
|
empresa_integracao = empresa_integracao,
|
||||||
|
indexador_integracao = indexador_integracao,
|
||||||
|
inscricao_estadual = inscricao_estadual,
|
||||||
|
usuario_sienge = usuario_sienge,
|
||||||
|
token_sienge = token_sienge,
|
||||||
|
oauth = oauth
|
||||||
|
)
|
||||||
|
self.session.add(config)
|
||||||
|
self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao inserir configuração do cliente: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def update_config_cliente(self, tenant, cpf_cnpj, empresa_integracao, indexador_integracao, inscricao_estadual, usuario_sienge, token_sienge, oauth):
|
||||||
|
try:
|
||||||
|
config = self.session.query(ConfigTenant).filter(ConfigTenant.tenant == tenant).first()
|
||||||
|
if config:
|
||||||
|
config.cpf_cnpj = cpf_cnpj
|
||||||
|
config.empresa_integracao = empresa_integracao
|
||||||
|
config.indexador_integracao = indexador_integracao
|
||||||
|
config.inscricao_estadual = inscricao_estadual
|
||||||
|
config.usuario_sienge = usuario_sienge
|
||||||
|
config.token_sienge = token_sienge
|
||||||
|
config.oauth = oauth
|
||||||
|
self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar configuração do cliente: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
# MÉTODOS PARA GERENCIAMENTO DE APROPRIAÇÕES FINANCEIRAS - AQUI PODEMOS IMPLEMENTAR MÉTODOS PARA INSERIR, ATUALIZAR E CONSULTAR APROPRIAÇÕES FINANCEIRAS NO BANCO DE DADOS
|
||||||
|
def get_lista_apropriacao_financeira(self, tenant):
|
||||||
|
try:
|
||||||
|
apropriacao = self.session.query(ApropriacaoFinanceira).filter(
|
||||||
|
ApropriacaoFinanceira.tenant == tenant
|
||||||
|
).all()
|
||||||
|
return apropriacao
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao buscar apropriação financeira: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def insert_apropriacao_financeira(self, tenant, centro_custo, plano_financeiro, percentual_apropriacao):
|
||||||
|
try:
|
||||||
|
apropriacao = ApropriacaoFinanceira(
|
||||||
|
tenant = tenant,
|
||||||
|
centro_custo = centro_custo,
|
||||||
|
plano_financeiro = plano_financeiro,
|
||||||
|
percentual_apropriacao = percentual_apropriacao
|
||||||
|
)
|
||||||
|
self.session.add(apropriacao)
|
||||||
|
self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao inserir apropriação financeira: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
def update_apropriacao_financeira(self, tenant, centro_custo, plano_financeiro, percentual_apropriacao):
|
||||||
|
try:
|
||||||
|
apropriacao = self.session.query(ApropriacaoFinanceira).filter(
|
||||||
|
ApropriacaoFinanceira.tenant == tenant,
|
||||||
|
ApropriacaoFinanceira.centro_custo == centro_custo
|
||||||
|
).first()
|
||||||
|
if apropriacao:
|
||||||
|
apropriacao.plano_financeiro = plano_financeiro
|
||||||
|
apropriacao.percentual_apropriacao = percentual_apropriacao
|
||||||
|
self.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao atualizar apropriação financeira: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
|
|
||||||
|
# MÉTODOS PARA GERENCIAMENTO DE CREDENCIAIS - AQUI PODEMOS IMPLEMENTAR MÉTODOS PARA INSERIR, ATUALIZAR E CONSULTAR CREDENCIAIS DE SERVIÇOS INTEGRADORES NO BANCO DE DADOS
|
||||||
|
def get_credenciais_servicos_integradores(self, integrador):
|
||||||
|
try:
|
||||||
|
credenciais = self.session.query(CredenciaisServicosIntegradores).filter(
|
||||||
|
CredenciaisServicosIntegradores.integrador == integrador
|
||||||
|
).first()
|
||||||
|
return credenciais
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao buscar credenciais de serviços integradores: {e}")
|
||||||
|
finally:
|
||||||
|
self.session.close()
|
||||||
1
src/exchangeModels.py
Symbolic link
1
src/exchangeModels.py
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../utils/exchangeModels.py
|
||||||
102
src/models/plugnotas/services_model.py
Normal file
102
src/models/plugnotas/services_model.py
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class EnderecoNfe(BaseModel):
|
||||||
|
logradouro : str | None = None
|
||||||
|
numero : str | None = None
|
||||||
|
bairro : str | None = None
|
||||||
|
codigoCidade : str | None = None
|
||||||
|
tipoLogradouro : str | None = None
|
||||||
|
estado : str | None = None
|
||||||
|
cep : str | None = None
|
||||||
|
|
||||||
|
class EmitenteNfe(BaseModel):
|
||||||
|
cpfCnpj : str | None = None
|
||||||
|
inscricaoEstadual: str | None = None
|
||||||
|
|
||||||
|
class DestinatarioNfe(BaseModel):
|
||||||
|
cpfCnpj : str | None = None
|
||||||
|
razaoSocial: str | None = None
|
||||||
|
email : str | None = None
|
||||||
|
endereco : EnderecoNfe
|
||||||
|
|
||||||
|
class ValorUnitarioNfe(BaseModel):
|
||||||
|
comercial : float | None = None
|
||||||
|
tributavel: float | None = None
|
||||||
|
|
||||||
|
class BaseCalculoNfe(BaseModel):
|
||||||
|
modalidadeDeterminacao: int | None = None
|
||||||
|
valor : float | None = None
|
||||||
|
quantidade : float | None = None
|
||||||
|
|
||||||
|
class IcmsNfe(BaseModel):
|
||||||
|
origem : str | None = None
|
||||||
|
cst : str | None = None
|
||||||
|
baseCalculo: BaseCalculoNfe
|
||||||
|
aliquota : float | None = None
|
||||||
|
valor : float | None = None
|
||||||
|
|
||||||
|
class PisNfe(BaseModel):
|
||||||
|
cst : str | None = None
|
||||||
|
baseCalculo: BaseCalculoNfe
|
||||||
|
aliquota : float | None = None
|
||||||
|
valor : float | None = None
|
||||||
|
|
||||||
|
class CofinsNfe(BaseModel):
|
||||||
|
cst : str | None = None
|
||||||
|
baseCalculo: BaseCalculoNfe
|
||||||
|
aliquota : float | None = None
|
||||||
|
valor : float | None = None
|
||||||
|
|
||||||
|
class TributosNfe(BaseModel):
|
||||||
|
icms : IcmsNfe
|
||||||
|
pis : PisNfe
|
||||||
|
cofins: CofinsNfe
|
||||||
|
|
||||||
|
class ItensNfe(BaseModel):
|
||||||
|
codigo : str | None = None
|
||||||
|
descricao : str | None = None
|
||||||
|
ncm : str | None = None
|
||||||
|
cest : str | None = None
|
||||||
|
cfop : str | None = None
|
||||||
|
valorUnitario: ValorUnitarioNfe
|
||||||
|
valor : float | None = None
|
||||||
|
tributos : TributosNfe
|
||||||
|
|
||||||
|
class PagamentosNfe(BaseModel):
|
||||||
|
aVista: bool | None = None
|
||||||
|
meio : str | None = None
|
||||||
|
valor : float | None = None
|
||||||
|
|
||||||
|
class TelefoneNfe(BaseModel):
|
||||||
|
ddd : str | None = None
|
||||||
|
numero: str | None = None
|
||||||
|
|
||||||
|
class ResponsavelTecnicoNfe(BaseModel):
|
||||||
|
cpfCnpj : str | None = None
|
||||||
|
nome : str | None = None
|
||||||
|
email : str | None = None
|
||||||
|
telefone: TelefoneNfe | None = None
|
||||||
|
|
||||||
|
class DadosNfe(BaseModel):
|
||||||
|
idIntegracao : str | None = None
|
||||||
|
presencial : bool | None = None
|
||||||
|
consumidorFinal : bool | None = None
|
||||||
|
natureza : str | None = None
|
||||||
|
emitente : EmitenteNfe
|
||||||
|
destinatario : DestinatarioNfe
|
||||||
|
itens : list[ItensNfe]
|
||||||
|
pagamentos : list[PagamentosNfe]
|
||||||
|
responsavelTecnico: ResponsavelTecnicoNfe | None = None
|
||||||
|
|
||||||
|
class FilaRetorno(BaseModel):
|
||||||
|
system : str | None = None
|
||||||
|
service : str | None = None
|
||||||
|
serviceid : str | None = None
|
||||||
|
servicekey: str | None = None
|
||||||
|
|
||||||
|
class EmitirNfe(BaseModel):
|
||||||
|
fila_retorno: FilaRetorno
|
||||||
|
fila : str | None = None
|
||||||
|
metodo : str | None = None
|
||||||
|
cliente : str | None = None
|
||||||
|
dados : DadosNfe
|
||||||
19
src/models/recebimento.py
Normal file
19
src/models/recebimento.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from models.sg_tray_models.services_model import TrayWebhookPayload
|
||||||
|
|
||||||
|
class Metadata(BaseModel):
|
||||||
|
cliente : str
|
||||||
|
origem : str
|
||||||
|
timestamp: str
|
||||||
|
|
||||||
|
class Recebimento(BaseModel):
|
||||||
|
metadata: Metadata
|
||||||
|
data : TrayWebhookPayload
|
||||||
|
|
||||||
|
class RecebimentoPlugnotas(BaseModel):
|
||||||
|
id : str | None = None
|
||||||
|
status : str | None = None
|
||||||
|
documento : str | None = None
|
||||||
|
idIntegracao : str | None = None
|
||||||
|
emitente : str | None = None
|
||||||
|
xml_base64 : str | None = None
|
||||||
117
src/models/sienge/services_models.py
Normal file
117
src/models/sienge/services_models.py
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class Addresses(BaseModel):
|
||||||
|
type : str | None = None
|
||||||
|
streetName : str | None = None
|
||||||
|
number : str | None = None
|
||||||
|
complement : str | None = None
|
||||||
|
neighborhood : str | None = None
|
||||||
|
cityId : int | None = None
|
||||||
|
city : str | None = None
|
||||||
|
state : str | None = None
|
||||||
|
zipCode : str | None = None
|
||||||
|
|
||||||
|
class Phones(BaseModel):
|
||||||
|
number : str | None = None
|
||||||
|
main : bool | None = None
|
||||||
|
type : str | None = None
|
||||||
|
note : str | None = None
|
||||||
|
idd : str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class AddressesSpouse(BaseModel):
|
||||||
|
city : str | None = None
|
||||||
|
complement : str | None = None
|
||||||
|
neighborhood : str | None = None
|
||||||
|
number : str | None = None
|
||||||
|
streetName : str | None = None
|
||||||
|
zipCode : str | None = None
|
||||||
|
|
||||||
|
class Spouse(BaseModel) :
|
||||||
|
foreigner : str | None = None
|
||||||
|
internationalId : str | None = None
|
||||||
|
cpf : str | None = None
|
||||||
|
name : str | None = None
|
||||||
|
email : str | None = None
|
||||||
|
sex : str | None = None
|
||||||
|
civilStatus : str | None = None
|
||||||
|
birthDate : str | None = None
|
||||||
|
numberIdentityCard : str | None = None
|
||||||
|
issueDateIdentityCard: str | None = None
|
||||||
|
profession : str | None = None
|
||||||
|
nationality : str | None = None
|
||||||
|
birthPlace : str | None = None
|
||||||
|
fatherName : str | None = None
|
||||||
|
motherName : str | None = None
|
||||||
|
cellphoneNumber : str | None = None
|
||||||
|
businessPhone : str | None = None
|
||||||
|
company : str | None = None
|
||||||
|
addresses : AddressesSpouse | None = None
|
||||||
|
|
||||||
|
class NaturalPersonData(BaseModel):
|
||||||
|
name : str | None = None
|
||||||
|
email : str | None = None
|
||||||
|
birthDate : str | None = None
|
||||||
|
birthPlace : str | None = None
|
||||||
|
civilStatus : str | None = None
|
||||||
|
cpf : str | None = None
|
||||||
|
mailingAddress : str | None = None
|
||||||
|
licenseNumber : str | None = None
|
||||||
|
licenseIssuingBody : str | None = None
|
||||||
|
licenseIssueDate : str | None = None
|
||||||
|
fatherName : str | None = None
|
||||||
|
sex : str | None = None
|
||||||
|
issueDateIdentityCard : str | None = None
|
||||||
|
matrimonialRegime : str | None = None
|
||||||
|
marriageDate : str | None = None
|
||||||
|
issuingBody : str | None = None
|
||||||
|
nationality : str | None = None
|
||||||
|
numberIdentityCard : str | None = None
|
||||||
|
motherName : str | None = None
|
||||||
|
profession : str | None = None
|
||||||
|
spouse : Spouse | None = None
|
||||||
|
|
||||||
|
class Agents(BaseModel):
|
||||||
|
id : int | None = None
|
||||||
|
|
||||||
|
class LegalPersonData(BaseModel):
|
||||||
|
name : str | None = None
|
||||||
|
email : str | None = None
|
||||||
|
cityRegistrationNumber : str | None = None
|
||||||
|
cnaeNumber : str | None = None
|
||||||
|
cnpj : str | None = None
|
||||||
|
contactName : str | None = None
|
||||||
|
creaNumber : str | None = None
|
||||||
|
establishmentDate : str | None = None
|
||||||
|
fantasyName : str | None = None
|
||||||
|
note : str | None = None
|
||||||
|
site : str | None = None
|
||||||
|
shareCapital : float | None = None
|
||||||
|
stateRegistrationNumber : str | None = None
|
||||||
|
technicalManager : str | None = None
|
||||||
|
agents : list[Agents] | None = None
|
||||||
|
|
||||||
|
class FamilyIncome(BaseModel):
|
||||||
|
kinsName : str | None = None
|
||||||
|
kinship : str | None = None
|
||||||
|
incomeValue : float | None = None
|
||||||
|
observation : str | None = None
|
||||||
|
|
||||||
|
class PostCustomerSienge(BaseModel):
|
||||||
|
personType : str | None = None
|
||||||
|
foreigner : str | None = None
|
||||||
|
typeId : int | None = None
|
||||||
|
internationalId : str | None = None
|
||||||
|
subtypeIds : list[int] | None = None
|
||||||
|
addresses : list[Addresses] | None = None
|
||||||
|
phones : list[Phones] | None = None
|
||||||
|
naturalPersonData : NaturalPersonData | None = None
|
||||||
|
legalPersonData : LegalPersonData | None= None
|
||||||
|
familyIncome : list[FamilyIncome] | None= None
|
||||||
|
|
||||||
|
class Config(BaseModel):
|
||||||
|
subdominio: str | None = None
|
||||||
|
usuario : str
|
||||||
|
senha : str
|
||||||
|
tenant : str | None = None
|
||||||
|
cliente : str | None = None
|
||||||
26
src/send_queue.py
Normal file
26
src/send_queue.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from Exchange2 import QueueExchange2
|
||||||
|
|
||||||
|
version = "1.0.0" # Define the version of your service
|
||||||
|
|
||||||
|
class SendQueueService:
|
||||||
|
def __init__(self,system: str, service: str, amqps: str, origin: str, serviceid: str, servicekey: str):
|
||||||
|
try:
|
||||||
|
self.service = service
|
||||||
|
self.exchange = QueueExchange2(amqps = amqps,
|
||||||
|
origin = origin,
|
||||||
|
system = system,
|
||||||
|
service = service,
|
||||||
|
version = version)
|
||||||
|
self.exchange.setCrypto(serviceid, servicekey)
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def send_queue(self, payload: dict):
|
||||||
|
try:
|
||||||
|
self.exchange.setPayload(json.dumps(payload))
|
||||||
|
self.exchange.sendMsg(message=json.dumps({"queue": "OK"}), service=self.service)
|
||||||
|
return {"status": "success", "message": "Task sent successfully"}
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": str(e)}
|
||||||
37
src/send_rpc.py
Normal file
37
src/send_rpc.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from Exchange2 import RPCExchange2
|
||||||
|
|
||||||
|
version = "1.0.0" # Define the version of your service
|
||||||
|
|
||||||
|
class SendRPCService:
|
||||||
|
def __init__(self, system: str, service: str, amqps: str, origin: str, serviceid: str, servicekey: str):
|
||||||
|
try:
|
||||||
|
self.max_timeout = 15
|
||||||
|
self.exchange = RPCExchange2(amqps = amqps,
|
||||||
|
origin = origin,
|
||||||
|
system = system,
|
||||||
|
service = service,
|
||||||
|
version = version)
|
||||||
|
print(f"Conectando ao RabbitMQ em {amqps}...", flush=True)
|
||||||
|
print(f"system: {system}, service: {service}, origin: {origin}", flush=True)
|
||||||
|
self.exchange.setCrypto(serviceid, servicekey)
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def send_rpc(self, payload: dict):
|
||||||
|
try:
|
||||||
|
self.exchange.setPayload(json.dumps(payload))
|
||||||
|
response = self.exchange.callRPC(message=json.dumps({"task": "OK"}), timeout=self.max_timeout)
|
||||||
|
return json.loads(response)
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def get_payload(self, hmac, payload):
|
||||||
|
try:
|
||||||
|
if self.exchange.getPayload(hmac, payload):
|
||||||
|
return json.loads(self.exchange.payload)
|
||||||
|
else:
|
||||||
|
raise ValueError("Falha ao descifrar payload")
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
85
src/services/cliente.py
Normal file
85
src/services/cliente.py
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
from models.sienge.services_models import *
|
||||||
|
from models.sg_tray_models.services_model import TrayOrderRootResponse
|
||||||
|
from appIC2Builder import SystemConfig
|
||||||
|
from api.tray import ApiTray
|
||||||
|
from db.banco import ConfigTenant
|
||||||
|
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
|
import redis
|
||||||
|
|
||||||
|
class ClienteService:
|
||||||
|
def __init__(self, config : SystemConfig, redis_client : redis.Redis):
|
||||||
|
self.config = config
|
||||||
|
self.api_tray = ApiTray(redis_client=redis_client, system_config=config)
|
||||||
|
|
||||||
|
def _mapper_tray_to_sienge_cliente(self, pedido : TrayOrderRootResponse, config_tenant: ConfigTenant) -> PostCustomerSienge:
|
||||||
|
|
||||||
|
enderecos : list[Addresses] = []
|
||||||
|
try:
|
||||||
|
for endereco in pedido.Order.Customer.CustomerAddresses:
|
||||||
|
enderecos.append(Addresses(
|
||||||
|
cityId = None,
|
||||||
|
city = endereco.CustomerAddress.city,
|
||||||
|
state = endereco.CustomerAddress.state,
|
||||||
|
neighborhood = endereco.CustomerAddress.neighborhood,
|
||||||
|
number = endereco.CustomerAddress.number,
|
||||||
|
complement = endereco.CustomerAddress.complement,
|
||||||
|
streetName = endereco.CustomerAddress.address,
|
||||||
|
zipCode = endereco.CustomerAddress.zip_code,
|
||||||
|
type = 'C' if endereco.CustomerAddress.description == "Comercial" else 'R'
|
||||||
|
))
|
||||||
|
except ValidationError as e:
|
||||||
|
print(f"Erro ao mapear endereços: {e}",flush=True)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
try:
|
||||||
|
telefones = [Phones(
|
||||||
|
type = "CE",
|
||||||
|
number = pedido.Order.Customer.cellphone,
|
||||||
|
main = True
|
||||||
|
)]
|
||||||
|
except ValidationError as e:
|
||||||
|
print(f"Erro ao mapear telefones: {e}",flush=True)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
try:
|
||||||
|
if pedido.Order.Customer.cpf is not None and pedido.Order.Customer.cpf != "":
|
||||||
|
cliente = PostCustomerSienge(
|
||||||
|
addresses = enderecos,
|
||||||
|
phones = telefones,
|
||||||
|
naturalPersonData = NaturalPersonData(
|
||||||
|
name = pedido.Order.Customer.name,
|
||||||
|
cpf = pedido.Order.Customer.cpf,
|
||||||
|
email = pedido.Order.Customer.email,
|
||||||
|
birthDate = pedido.Order.Customer.birth_date,
|
||||||
|
sex = 'M' if pedido.Order.Customer.gender == "0" else 'F',
|
||||||
|
numberIdentityCard=pedido.Order.Customer.rg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif pedido.Order.Customer.cnpj is not None and pedido.Order.Customer.cnpj != "":
|
||||||
|
cliente = PostCustomerSienge(
|
||||||
|
addresses = enderecos,
|
||||||
|
phones = telefones,
|
||||||
|
legalPersonData = LegalPersonData(
|
||||||
|
name = pedido.Order.Customer.company_name,
|
||||||
|
email = pedido.Order.Customer.email,
|
||||||
|
cnpj = pedido.Order.Customer.cnpj,
|
||||||
|
stateRegistrationNumber = pedido.Order.Customer.state_inscription
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError("Cliente sem CPF ou CNPJ")
|
||||||
|
return {
|
||||||
|
"config": {
|
||||||
|
"subdominio": "s8psasistemas", #config_tenant.tenant if not config_tenant.oauth else None,
|
||||||
|
"usuario" : config_tenant.usuario_sienge,
|
||||||
|
"senha" : config_tenant.token_sienge,
|
||||||
|
"tenant" : config_tenant.tenant if config_tenant.oauth else None,
|
||||||
|
"cliente" : config_tenant.tenant
|
||||||
|
},
|
||||||
|
"cliente": cliente.model_dump()
|
||||||
|
}
|
||||||
|
except ValidationError as e:
|
||||||
|
print(f"Erro ao mapear cliente: {e}",flush=True)
|
||||||
|
raise e
|
||||||
198
src/services/nfe.py
Normal file
198
src/services/nfe.py
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
from models.plugnotas.services_model import DadosNfe
|
||||||
|
from models.sg_tray_models.services_model import TrayOrderRootResponse
|
||||||
|
from models.plugnotas.services_model import *
|
||||||
|
from appIC2Builder import SystemConfig
|
||||||
|
|
||||||
|
from db.banco import ConfigTenant
|
||||||
|
from api.tray import ApiTray
|
||||||
|
from api.publicas import busca_cep
|
||||||
|
|
||||||
|
import redis
|
||||||
|
|
||||||
|
class NfeService:
|
||||||
|
def __init__(self, config : SystemConfig, redis : redis.Redis):
|
||||||
|
self.config = config
|
||||||
|
self.api_tray = ApiTray(redis_client=redis, system_config=config)
|
||||||
|
self.meios_pagamento_plugnotas = {
|
||||||
|
"01" : "Dinheiro",
|
||||||
|
"02" : "Cheque",
|
||||||
|
"03" : "Cartão de Crédito",
|
||||||
|
"04" : "Cartão de Débito",
|
||||||
|
"05" : "Cartão da Loja (Private Label), Crediário Digital, Outros Crediários",
|
||||||
|
"10" : "Vale Alimentação",
|
||||||
|
"11" : "Vale Refeição",
|
||||||
|
"12" : "Vale Presente",
|
||||||
|
"13" : "Vale Combustível",
|
||||||
|
"14" : "Duplicata Mercantil",
|
||||||
|
"15" : "Boleto Bancário",
|
||||||
|
"16" : "Depósito Bancário",
|
||||||
|
"17" : "Pagamento Instantâneo (PIX) - Dinâmico",
|
||||||
|
"18" : "TED (Transferência Eletrônica Disponível)",
|
||||||
|
"19" : "Programa de fidelidade, Cashback, Crédito Virtual",
|
||||||
|
"20" : "Pagamento Instantâneo (PIX) - Estático",
|
||||||
|
"21" : "Crédito em Loja",
|
||||||
|
"22" : "Pagamento Eletrônico não Informado - falha de hardware do sistema emissor",
|
||||||
|
"23" : "Pagamento Instantâneo (PIX) - Automático",
|
||||||
|
"24" : "TEF - (Book Transfer)",
|
||||||
|
"90" : "Sem pagamento",
|
||||||
|
"91" : "Pagamento Posterior",
|
||||||
|
"99" : "Outros"
|
||||||
|
}
|
||||||
|
|
||||||
|
def localiza_tipo_logradouro(self, logradouro : str) -> str:
|
||||||
|
match logradouro.split(" ")[0].upper():
|
||||||
|
case "AVENIDA":
|
||||||
|
return "Avenida"
|
||||||
|
case "ALAMEDA":
|
||||||
|
return "Alameda"
|
||||||
|
case "CHACARA":
|
||||||
|
return "Chácara"
|
||||||
|
case "COLONIA":
|
||||||
|
return "Colônia"
|
||||||
|
case "CONDOMINIO":
|
||||||
|
return "Condomínio"
|
||||||
|
case "EQNP":
|
||||||
|
return "Eqnp"
|
||||||
|
case "ESTANCIA":
|
||||||
|
return "Estância"
|
||||||
|
case "ESTRADA":
|
||||||
|
return "Estrada"
|
||||||
|
case "FAZENDA":
|
||||||
|
return "Fazenda"
|
||||||
|
case "PRACA":
|
||||||
|
return "Praça"
|
||||||
|
case "PROLONGAMENTO":
|
||||||
|
return "Prolongamento"
|
||||||
|
case "RODOVIA":
|
||||||
|
return "Rodovia"
|
||||||
|
case "RUA":
|
||||||
|
return "Rua"
|
||||||
|
case "SITIO":
|
||||||
|
return "Sítio"
|
||||||
|
case "TRAVESSA":
|
||||||
|
return "Travessa"
|
||||||
|
case "VICINAL":
|
||||||
|
return "Vicinal"
|
||||||
|
case _:
|
||||||
|
return "Rua"
|
||||||
|
|
||||||
|
def localiza_meio_pagamento(self, pagamento : str) -> str:
|
||||||
|
for key, value in self.meios_pagamento_plugnotas.items():
|
||||||
|
if pagamento.upper() in value.upper():
|
||||||
|
return key
|
||||||
|
return "99"
|
||||||
|
|
||||||
|
def _mapper_pedido_tray_to_nfe(self, pedido_tray : TrayOrderRootResponse, config_tenant : ConfigTenant) -> EmitirNfe:
|
||||||
|
try:
|
||||||
|
emitente_nfe = EmitenteNfe(
|
||||||
|
cpfCnpj = config_tenant.cpf_cnpj,
|
||||||
|
inscricaoEstadual = config_tenant.inscricao_estadual
|
||||||
|
)
|
||||||
|
|
||||||
|
cep_info = busca_cep(pedido_tray.Order.Customer.zip_code)
|
||||||
|
|
||||||
|
if 'erro' in cep_info:
|
||||||
|
raise Exception(f"Erro ao buscar CEP: {cep_info['erro']}")
|
||||||
|
|
||||||
|
endereco_destinatario = EnderecoNfe(
|
||||||
|
bairro = cep_info['bairro'],
|
||||||
|
cep = cep_info['cep'],
|
||||||
|
codigoCidade = cep_info['ibge'],
|
||||||
|
estado = cep_info['uf'],
|
||||||
|
logradouro = cep_info['logradouro'],
|
||||||
|
numero = pedido_tray.Order.Customer.number,
|
||||||
|
tipoLogradouro = self.localiza_tipo_logradouro(cep_info['logradouro'])
|
||||||
|
)
|
||||||
|
|
||||||
|
destinatario_nfe = DestinatarioNfe(
|
||||||
|
cpfCnpj = pedido_tray.Order.Customer.cpf if pedido_tray.Order.Customer.cpf else pedido_tray.Order.Customer.cnpj,
|
||||||
|
razaoSocial = pedido_tray.Order.Customer.name,
|
||||||
|
email = pedido_tray.Order.Customer.email,
|
||||||
|
endereco = endereco_destinatario
|
||||||
|
)
|
||||||
|
itens_nfe = list[ItensNfe]()
|
||||||
|
|
||||||
|
for item in pedido_tray.Order.ProductsSold:
|
||||||
|
item_nfe = ItensNfe(
|
||||||
|
codigo = item.ProductsSold.product_id,
|
||||||
|
descricao = item.ProductsSold.name,
|
||||||
|
ncm = item.ProductsSold.ncm,
|
||||||
|
cfop = "", # Implementar
|
||||||
|
valorUnitario = ValorUnitarioNfe(
|
||||||
|
comercial = item.ProductsSold.price,
|
||||||
|
tributavel = item.ProductsSold.price
|
||||||
|
),
|
||||||
|
valor = item.ProductsSold.price,
|
||||||
|
tributos = TributosNfe(
|
||||||
|
icms=IcmsNfe(
|
||||||
|
origem = "0", # Implementar
|
||||||
|
cst = "00", # Implementar
|
||||||
|
baseCalculo=BaseCalculoNfe(
|
||||||
|
modalidadeDeterminacao = 0,
|
||||||
|
valor = 0,
|
||||||
|
quantidade = 0
|
||||||
|
),
|
||||||
|
aliquota = 0,
|
||||||
|
valor = 0
|
||||||
|
),
|
||||||
|
pis=PisNfe(
|
||||||
|
cst="99", # Implementar
|
||||||
|
baseCalculo=BaseCalculoNfe(
|
||||||
|
modalidadeDeterminacao = 0,
|
||||||
|
valor = 0,
|
||||||
|
quantidade = 0
|
||||||
|
),
|
||||||
|
aliquota = 0,
|
||||||
|
valor = 0
|
||||||
|
),
|
||||||
|
cofins=CofinsNfe(
|
||||||
|
cst="07", # Implementar
|
||||||
|
baseCalculo=BaseCalculoNfe(
|
||||||
|
modalidadeDeterminacao = 0,
|
||||||
|
valor = 0,
|
||||||
|
quantidade = 0
|
||||||
|
),
|
||||||
|
aliquota = 0,
|
||||||
|
valor = 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
itens_nfe.append(item_nfe)
|
||||||
|
|
||||||
|
pagamentos_nfe = list[PagamentosNfe]()
|
||||||
|
|
||||||
|
for pagamento in pedido_tray.Order.Payment:
|
||||||
|
pagamento_nfe = PagamentosNfe(
|
||||||
|
meio = self.localiza_meio_pagamento(pagamento.Payment.method),
|
||||||
|
valor = pagamento.Payment.value
|
||||||
|
)
|
||||||
|
pagamentos_nfe.append(pagamento_nfe)
|
||||||
|
|
||||||
|
dados_nfe = DadosNfe(
|
||||||
|
idIntegracao = f"PE_{pedido_tray.Order.id}",
|
||||||
|
emitente = emitente_nfe,
|
||||||
|
destinatario = destinatario_nfe,
|
||||||
|
itens = itens_nfe,
|
||||||
|
pagamentos = pagamentos_nfe
|
||||||
|
)
|
||||||
|
|
||||||
|
fila_retorno = FilaRetorno(
|
||||||
|
service = self.config.service,
|
||||||
|
serviceid = self.config.serviceid,
|
||||||
|
servicekey = self.config.servicekey,
|
||||||
|
system = self.config.system
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
nfe = EmitirNfe(
|
||||||
|
fila_retorno = fila_retorno,
|
||||||
|
fila = "nfe",
|
||||||
|
metodo = "post",
|
||||||
|
cliente = self.config.client,
|
||||||
|
dados = dados_nfe
|
||||||
|
)
|
||||||
|
return nfe
|
||||||
|
except Exception as e:
|
||||||
|
print(e ,flush=True)
|
||||||
|
raise Exception(f"Erro ao mapear pedido: {e}")
|
||||||
|
|
||||||
51
src/services/titulo_receber.py
Normal file
51
src/services/titulo_receber.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
from models.servico_titulos_receber.contracts_Tittles import ReceivableBillPayload, SiengeCredentials
|
||||||
|
from models.servico_titulos_receber.tittle_model import PostAccountsReceivableReceivableBills, FinancialAppropriations
|
||||||
|
from models.sg_tray_models.services_model import TrayOrderRootResponse
|
||||||
|
from appIC2Builder import SystemConfig
|
||||||
|
from db.banco import ConfigTenant, ApropriacaoFinanceira, Pedido
|
||||||
|
|
||||||
|
class TituloReceberService:
|
||||||
|
def __init__(self, config: SystemConfig):
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def map_tray_order_to_bill_data(self, tray_order : TrayOrderRootResponse, config_tenant: ConfigTenant, apropriacao: list[ApropriacaoFinanceira], pedido: Pedido, chave_nfe: str, numero_nfe: str) -> ReceivableBillPayload:
|
||||||
|
|
||||||
|
credenciais = SiengeCredentials(
|
||||||
|
subdomain = "s8psasistemas", #config_tenant.tenant,
|
||||||
|
username = config_tenant.usuario_sienge,
|
||||||
|
password = config_tenant.token_sienge
|
||||||
|
)
|
||||||
|
|
||||||
|
apropriacoes = [FinancialAppropriations(
|
||||||
|
costCenterId = item.centro_custo,
|
||||||
|
paymentCategoryId = item.plano_financeiro,
|
||||||
|
percentage = item.percentual_apropriacao,
|
||||||
|
amount = tray_order.Order.total
|
||||||
|
) for item in apropriacao]
|
||||||
|
|
||||||
|
|
||||||
|
bill_data = PostAccountsReceivableReceivableBills(
|
||||||
|
companyId = config_tenant.empresa_integracao,
|
||||||
|
customerId = pedido.codigo_cliente,
|
||||||
|
document = "NF",
|
||||||
|
documentNumber = numero_nfe,
|
||||||
|
issueDate = tray_order.Order.date,
|
||||||
|
competenceDate = tray_order.Order.date,
|
||||||
|
totalInvoiceAmount = tray_order.Order.total,
|
||||||
|
installmentsNumber = 1,#tray_order.Order.installment,
|
||||||
|
accountingDate = tray_order.Order.date,
|
||||||
|
indexerId = config_tenant.indexador_integracao,
|
||||||
|
baseDate = tray_order.Order.date,
|
||||||
|
dueDate = tray_order.Order.date,
|
||||||
|
notes = f"Titulo referente ao pedido {tray_order.Order.id} da plataforma Tray. Chave de acesso da NF-e: {chave_nfe}",
|
||||||
|
financialAppropriations = apropriacoes,
|
||||||
|
installments = None
|
||||||
|
)
|
||||||
|
|
||||||
|
integrador = ReceivableBillPayload(
|
||||||
|
tenant=self.config.client,
|
||||||
|
bill_data=bill_data,
|
||||||
|
credentials=credenciais
|
||||||
|
)
|
||||||
|
|
||||||
|
return integrador
|
||||||
201
src/teste.py
Normal file
201
src/teste.py
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
from models.sienge.services_models import Config
|
||||||
|
from models.plugnotas.services_model import *
|
||||||
|
# from api.tray import ApiTray
|
||||||
|
import json, redis
|
||||||
|
from models.sienge.services_models import *
|
||||||
|
from models.sg_tray_models.services_model import TrayOrderRootResponse
|
||||||
|
from send_rpc import SendRPCService
|
||||||
|
|
||||||
|
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
|
# from api.publicas import busca_cep
|
||||||
|
|
||||||
|
# rd = redis.Redis(host='localhost', port=6388, decode_responses=True)
|
||||||
|
|
||||||
|
# with open("modelo_envio_plugnotas.json", "r") as f:
|
||||||
|
# payload = json.load(f)
|
||||||
|
|
||||||
|
# emitir_nfe = EmitirNfe(**payload)
|
||||||
|
|
||||||
|
# print(emitir_nfe.model_dump_json(indent=4))
|
||||||
|
|
||||||
|
# api_tray = ApiTray(url='https://1225878.commercesuite.com.br',
|
||||||
|
# consumer_key='62598c0d55e7febda429d69a134dad3ac1adadc895e697e62df0ae214df62790',
|
||||||
|
# consumer_secret='7502e1994fd19108c2fa73a08559a8c76abc6e96a067057848152c78ec38ec66',
|
||||||
|
# code='beee2e6c9af8820622a72830862ca0f2c04ebc3859eed19ea6cffa799b938da4',
|
||||||
|
# redis_client=rd,
|
||||||
|
# service='controller',
|
||||||
|
# system='integrador',
|
||||||
|
# refresh_token=None)
|
||||||
|
# print(api_tray.get_pedido_completo(id_pedido=16101).model_dump())
|
||||||
|
|
||||||
|
# ceps = [
|
||||||
|
# "01001000", "01310100", "02020000", "03150000", "04567000",
|
||||||
|
# "05010000", "06050000", "07210000", "08021000", "09530000",
|
||||||
|
# "11035000", "12070000", "13083000", "14025000", "15015000",
|
||||||
|
# "16050000", "17012000", "18095000", "19061000", "19990000",
|
||||||
|
# "20040000", "20550000", "21040000", "22070002", "23050000",
|
||||||
|
# "24030000", "25010000", "26020000", "27035000", "28013000",
|
||||||
|
# "25555000", "26530000", "27280000", "27510000", "28155000",
|
||||||
|
# "28470000", "28625000", "28735000", "28890000", "28950000",
|
||||||
|
# "30110000", "30550000", "31030000", "32010000", "33025000",
|
||||||
|
# "34010000", "35020000", "36015000", "37010000", "38010000",
|
||||||
|
# "39010000", "34120000", "35501000", "36570000", "37540000",
|
||||||
|
# "38500000", "39400000", "39803000", "39950000", "39700000",
|
||||||
|
# "40015000", "41020000", "42010000", "43050000", "44015000",
|
||||||
|
# "45025000", "46010000", "47020000", "48015000", "48950000",
|
||||||
|
# "40170000", "41230000", "42500000", "43570000", "44580000",
|
||||||
|
# "45530000", "46570000", "47520000", "48550000", "48760000",
|
||||||
|
# "80010000", "80530000", "81050000", "82020000", "83005000",
|
||||||
|
# "84010000", "85012000", "86020000", "87013000", "88010000",
|
||||||
|
# "89010000", "80050100", "81560000", "83203000", "84266000",
|
||||||
|
# "85200000", "86200000", "87210000", "88220000", "87990000",
|
||||||
|
# "72800010", "74015000", "74120090", "74450000", "74810100",
|
||||||
|
# "75020000", "75503000", "75800000", "75901000", "76010000",
|
||||||
|
# "76300000", "73710000", "74230000", "74553000", "74934000",
|
||||||
|
# "75110000", "75690000", "76190000", "76400000", "76780000",
|
||||||
|
# "90010000", "91020000", "92010000", "93020000", "94010000",
|
||||||
|
# "95020000", "96015000", "97010000", "98020000", "99010000",
|
||||||
|
# "90550000", "91530000", "92510000", "93520000", "94560000",
|
||||||
|
# "95570000", "96508000", "97510000", "98560000", "99520000",
|
||||||
|
# "88010000", "88101000", "88220000", "88301000", "88400000",
|
||||||
|
# "88501000", "88600000", "88701000", "88805000", "88901000",
|
||||||
|
# "89010000", "89107000", "89201000", "89301000", "89460000",
|
||||||
|
# "89520000", "89600000", "89700000", "89801000", "89990000",
|
||||||
|
# "50030000", "51020000", "52010000", "53020000", "54010000",
|
||||||
|
# "55020000", "56010000", "56500000", "56750000", "56990000",
|
||||||
|
# "50710000", "51230000", "52250000", "53210000", "54280000",
|
||||||
|
# "55295000", "56200000", "56650000", "56890000", "56950000",
|
||||||
|
# "60020000", "60530000", "61010000", "62010000", "63020000",
|
||||||
|
# "60175000", "60630000", "61150000", "62110000", "63180000",
|
||||||
|
# "60250000", "60744000", "61200000", "62200000", "63260000",
|
||||||
|
# "60330000", "60860000", "61350000", "62320000", "63990000"
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# for cep in ceps:
|
||||||
|
# print(f"Buscando CEP: {cep}")
|
||||||
|
# print(busca_cep(cep))
|
||||||
|
# print("-" * 50)
|
||||||
|
|
||||||
|
# meios_pagamento_plugnotas = {
|
||||||
|
# "01" : "Dinheiro",
|
||||||
|
# "02" : "Cheque",
|
||||||
|
# "03" : "Cartão de Crédito",
|
||||||
|
# "04" : "Cartão de Débito",
|
||||||
|
# "05" : "Cartão da Loja (Private Label), Crediário Digital, Outros Crediários",
|
||||||
|
# "10" : "Vale Alimentação",
|
||||||
|
# "11" : "Vale Refeição",
|
||||||
|
# "12" : "Vale Presente",
|
||||||
|
# "13" : "Vale Combustível",
|
||||||
|
# "14" : "Duplicata Mercantil",
|
||||||
|
# "15" : "Boleto Bancário",
|
||||||
|
# "16" : "Depósito Bancário",
|
||||||
|
# "17" : "Pagamento Instantâneo (PIX) - Dinâmico",
|
||||||
|
# "18" : "TED (Transferência Eletrônica Disponível)",
|
||||||
|
# "19" : "Programa de fidelidade, Cashback, Crédito Virtual",
|
||||||
|
# "20" : "Pagamento Instantâneo (PIX) - Estático",
|
||||||
|
# "21" : "Crédito em Loja",
|
||||||
|
# "22" : "Pagamento Eletrônico não Informado - falha de hardware do sistema emissor",
|
||||||
|
# "23" : "Pagamento Instantâneo (PIX) - Automático",
|
||||||
|
# "24" : "TEF - (Book Transfer)",
|
||||||
|
# "90" : "Sem pagamento",
|
||||||
|
# "91" : "Pagamento Posterior",
|
||||||
|
# "99" : "Outros"
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# def localiza_meio_pagamento(pagamento : str) -> str:
|
||||||
|
# for key, value in meios_pagamento_plugnotas.items():
|
||||||
|
# if pagamento.upper() in value.upper():
|
||||||
|
# return key
|
||||||
|
# return "99"
|
||||||
|
|
||||||
|
# print(localiza_meio_pagamento("PIX"))
|
||||||
|
|
||||||
|
|
||||||
|
def _mapper_tray_to_sienge_cliente(pedido : TrayOrderRootResponse) -> PostCustomerSienge:
|
||||||
|
|
||||||
|
enderecos : list[Addresses] = []
|
||||||
|
try:
|
||||||
|
for endereco in pedido.Order.Customer.CustomerAddresses:
|
||||||
|
enderecos.append(Addresses(
|
||||||
|
cityId = None,
|
||||||
|
city = endereco.CustomerAddress.city,
|
||||||
|
state = endereco.CustomerAddress.state,
|
||||||
|
neighborhood = endereco.CustomerAddress.neighborhood,
|
||||||
|
number = endereco.CustomerAddress.number,
|
||||||
|
complement = endereco.CustomerAddress.complement,
|
||||||
|
streetName = endereco.CustomerAddress.address,
|
||||||
|
zipCode = endereco.CustomerAddress.zip_code,
|
||||||
|
type = 'R'
|
||||||
|
))
|
||||||
|
except ValidationError as e:
|
||||||
|
print(f"Erro ao mapear endereços: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
try:
|
||||||
|
telefones = [Phones(
|
||||||
|
type = "CE",
|
||||||
|
number = pedido.Order.Customer.cellphone,
|
||||||
|
main = True
|
||||||
|
)]
|
||||||
|
except ValidationError as e:
|
||||||
|
print(f"Erro ao mapear telefones: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
try:
|
||||||
|
if pedido.Order.Customer.cpf is not None and pedido.Order.Customer.cpf != "":
|
||||||
|
cliente = PostCustomerSienge(
|
||||||
|
addresses = enderecos,
|
||||||
|
phones = telefones,
|
||||||
|
naturalPersonData = NaturalPersonData(
|
||||||
|
name = pedido.Order.Customer.name,
|
||||||
|
cpf = pedido.Order.Customer.cpf,
|
||||||
|
email = pedido.Order.Customer.email,
|
||||||
|
birthDate = pedido.Order.Customer.birth_date,
|
||||||
|
sex = 'M' if pedido.Order.Customer.gender == "0" else 'F',
|
||||||
|
numberIdentityCard=pedido.Order.Customer.rg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif pedido.Order.Customer.cnpj is not None and pedido.Order.Customer.cnpj != "":
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
return cliente
|
||||||
|
except ValidationError as e:
|
||||||
|
print(f"Erro ao mapear cliente: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
with open("pedido_completo.json", "r") as f:
|
||||||
|
payload = json.load(f)
|
||||||
|
|
||||||
|
pedido = TrayOrderRootResponse(**payload)
|
||||||
|
cliente = _mapper_tray_to_sienge_cliente(pedido).model_dump()
|
||||||
|
config = Config(
|
||||||
|
subdominio = "s8psasistemas",
|
||||||
|
usuario = "s8psasistemas-midd",
|
||||||
|
senha = "fq3LdZzvTJq8u8oeASFhVkYV20NIac41",
|
||||||
|
tenant = None,
|
||||||
|
cliente = "s8psasistemas"
|
||||||
|
).model_dump()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"cliente" : cliente,
|
||||||
|
"config" : config
|
||||||
|
}
|
||||||
|
|
||||||
|
with open("payload.json", "w") as f:
|
||||||
|
json.dump(payload, f, indent=4)
|
||||||
|
|
||||||
|
exchange = SendRPCService(
|
||||||
|
system = "integrador_clientes",
|
||||||
|
service = "controller",
|
||||||
|
amqps = "amqps://integracao-vendas:R2E2xV4HHQYSjvlIOPeoA@tall-cyan-dog.rmq4.cloudamqp.com/integracao-vendas",
|
||||||
|
origin = "pc-balt",
|
||||||
|
serviceid = "98765432109876543210987654321098",
|
||||||
|
servicekey = "ZYXWVUTSRQPONMLKJIHGFEDCBA654321"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(exchange.send_rpc(payload))
|
||||||
14
src/testeNfe.py
Normal file
14
src/testeNfe.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
from send_queue import SendQueueService
|
||||||
|
import json
|
||||||
|
|
||||||
|
with open("modelo_webhook_plugnotas.json", "r") as f:
|
||||||
|
payload = json.load(f)
|
||||||
|
|
||||||
|
queue_service = SendQueueService(amqps = "amqps://integracao-vendas:R2E2xV4HHQYSjvlIOPeoA@tall-cyan-dog.rmq4.cloudamqp.com/integracao-vendas",
|
||||||
|
service = "controller",
|
||||||
|
system = "innovar_amr_tray",
|
||||||
|
origin = "pc_balt",
|
||||||
|
serviceid = "G7q7VkP587Kf",
|
||||||
|
servicekey = "EImD4TXc1PIi4V8jbkRapyhv9veqlg7Q")
|
||||||
|
|
||||||
|
print(queue_service.send_queue(payload))
|
||||||
6
webhook.json
Normal file
6
webhook.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"scope_name": "order",
|
||||||
|
"scope_id": "16527",
|
||||||
|
"seller_id": "1225878",
|
||||||
|
"act": "insert"
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue