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