PRIMEIRO COMMIT
This commit is contained in:
commit
ad749f1009
223 changed files with 34924 additions and 0 deletions
18
Models/config.py
Normal file
18
Models/config.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
# Dados de conexão ao banco de dados isolado do cliente e da API PlugNotas
|
||||
class Config(BaseModel):
|
||||
api_key: str | None = None
|
||||
x_api_key: str | None = None
|
||||
user_postgres: str
|
||||
pass_postgres: str
|
||||
host_postgres: str
|
||||
port_postgres: str
|
||||
database_postgres: str
|
||||
|
||||
# Informações de mensageria para a troca de arquivos (AMQP) via subagent
|
||||
class SystemConfig(BaseModel):
|
||||
serviceid: str
|
||||
servicekey: str
|
||||
origin: str
|
||||
amqps: str
|
||||
97
Models/empresa.py
Normal file
97
Models/empresa.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
###################
|
||||
## POST /empresas #
|
||||
###################
|
||||
class Endereco(BaseModel):
|
||||
codigoPais: str
|
||||
descricaoPais: str
|
||||
tipoLogradouro: str | None = None
|
||||
logradouro: str
|
||||
numero: str
|
||||
complemento: str | None = None
|
||||
tipoBairro: str | None = None
|
||||
bairro: str
|
||||
codigoCidade: str
|
||||
descricaoCidade: str
|
||||
estado: str
|
||||
cep: str
|
||||
|
||||
class Rps(BaseModel):
|
||||
lote: int
|
||||
numeracao: list
|
||||
|
||||
class Email(BaseModel):
|
||||
envio: bool
|
||||
|
||||
class Config(BaseModel):
|
||||
rps: Rps | None = None
|
||||
email: Email | None = None
|
||||
producao: bool | None = None
|
||||
envioLimitado: bool | None = None
|
||||
integracoes: list | None = None
|
||||
prefeitura: dict | str | int | None = None
|
||||
|
||||
class Nfse(BaseModel):
|
||||
config: Config | None = None
|
||||
ativo: bool = False
|
||||
tipoContrato: int | None = 0
|
||||
|
||||
class Dfe(BaseModel):
|
||||
primeiroNsu: bool
|
||||
|
||||
class ConfigNfe(BaseModel):
|
||||
dfe: Dfe | None = None
|
||||
email: Email | None = None
|
||||
integracoes: dict | None = None
|
||||
producao: bool | None = None
|
||||
impressaoFcp: bool | None = None
|
||||
impressaoPartilha: bool | None = None
|
||||
versaoManual: str | None = None
|
||||
versaoEsquema: str | None = None
|
||||
numeracao: list | None = None
|
||||
calculoAutomaticoIbpt: bool | None = True
|
||||
|
||||
class Nfe(BaseModel):
|
||||
config: ConfigNfe | None = None
|
||||
ativo: bool = False
|
||||
tipoContrato: int | None = 0
|
||||
|
||||
class ConfigNfce(BaseModel):
|
||||
email: Email | None = None
|
||||
integracoes: dict | None = None
|
||||
producao: bool | None = None
|
||||
versaoManual: str | None = None
|
||||
versaoEsquema: str | None = None
|
||||
numeracao: list | None = None
|
||||
|
||||
class Nfce(BaseModel):
|
||||
config: ConfigNfce | None = None
|
||||
ativo: bool = False
|
||||
tipoContrato: int | None = 0
|
||||
|
||||
|
||||
# Entidade Empresa que cadastra a credencial emissora no sistema da PlugNotas
|
||||
class Empresa(BaseModel):
|
||||
endereco: Endereco
|
||||
nfse: Nfse | None = None
|
||||
nfe: Nfe | None = None
|
||||
nfce: Nfce | None = None
|
||||
incentivoFiscal: bool
|
||||
incentivadorCultural: bool
|
||||
cpfCnpj: str
|
||||
inscricaoMunicipal: str
|
||||
inscricaoEstadual: str | None = None
|
||||
certificado: str
|
||||
razaoSocial: str
|
||||
nomeFantasia: str | None = None
|
||||
simplesNacional: bool
|
||||
regimeTributario: int
|
||||
regimeTributarioEspecial: int
|
||||
id: str | None = None
|
||||
banco: str | None = None
|
||||
email: str | None = None
|
||||
id_empresa: int | None = None
|
||||
|
||||
|
||||
115
Models/nfe.py
Normal file
115
Models/nfe.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
from pydantic import BaseModel
|
||||
from typing import List, Optional, Literal
|
||||
|
||||
EstadoEnum = Literal[
|
||||
"AC", "AL", "AM", "AP", "BA", "CE", "DF", "ES", "GO", "MA", "MG", "MS", "MT",
|
||||
"PA", "PB", "PE", "PI", "PR", "RJ", "RN", "RO", "RR", "RS", "SC", "SE", "SP", "TO"
|
||||
]
|
||||
|
||||
TipoLogradouroEnum = Literal[
|
||||
"Alameda", "Avenida", "Chácara", "Colônia", "Condomínio", "Estância", "Estrada",
|
||||
"Fazenda", "Praça", "Prolongamento", "Rodovia", "Rua", "Sítio", "Travessa", "Vicinal", "Eqnp"
|
||||
]
|
||||
|
||||
class Emitente(BaseModel):
|
||||
cpfCnpj: str
|
||||
inscricaoEstadual: Optional[str] = None
|
||||
|
||||
class Endereco(BaseModel):
|
||||
logradouro: str
|
||||
numero: str
|
||||
bairro: str
|
||||
codigoCidade: str
|
||||
estado: EstadoEnum
|
||||
cep: str
|
||||
tipoLogradouro: TipoLogradouroEnum
|
||||
codigoPais: Optional[str] = "1058"
|
||||
complemento: Optional[str] = None
|
||||
descricaoCidade: Optional[str] = None
|
||||
descricaoPais: Optional[str] = "Brasil"
|
||||
|
||||
class Destinatario(BaseModel):
|
||||
cpfCnpj: str
|
||||
razaoSocial: str
|
||||
nomeFantasia: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
inscricaoEstadual: Optional[str] = None
|
||||
inscricaoMunicipal: Optional[str] = None
|
||||
inscricaoSuframa: Optional[str] = None
|
||||
orgaoPublico: Optional[bool] = False
|
||||
endereco: Endereco
|
||||
|
||||
class ValorTributavel(BaseModel):
|
||||
comercial: float
|
||||
tributavel: float
|
||||
|
||||
class BaseCalculoIcms(BaseModel):
|
||||
modalidadeDeterminacao: int
|
||||
valor: float
|
||||
|
||||
class Icms(BaseModel):
|
||||
origem: str
|
||||
cst: str
|
||||
baseCalculo: BaseCalculoIcms
|
||||
aliquota: float
|
||||
valor: float
|
||||
|
||||
class BaseCalculoPis(BaseModel):
|
||||
valor: float
|
||||
quantidade: int
|
||||
|
||||
class Pis(BaseModel):
|
||||
cst: str
|
||||
baseCalculo: BaseCalculoPis
|
||||
aliquota: float
|
||||
valor: float
|
||||
|
||||
class BaseCalculoCofins(BaseModel):
|
||||
valor: float
|
||||
|
||||
class Cofins(BaseModel):
|
||||
cst: str
|
||||
baseCalculo: BaseCalculoCofins
|
||||
aliquota: float
|
||||
valor: float
|
||||
|
||||
class Tributos(BaseModel):
|
||||
icms: Optional[Icms] = None
|
||||
pis: Optional[Pis] = None
|
||||
cofins: Optional[Cofins] = None
|
||||
|
||||
class ItemNFe(BaseModel):
|
||||
codigo: str
|
||||
descricao: str
|
||||
ncm: str
|
||||
cest: Optional[str] = None
|
||||
cfop: str
|
||||
valorUnitario: ValorTributavel
|
||||
valor: float
|
||||
tributos: Tributos
|
||||
|
||||
class Pagamento(BaseModel):
|
||||
aVista: Optional[bool] = True
|
||||
meio: Optional[str] = "01"
|
||||
valor: float
|
||||
|
||||
class Telefone(BaseModel):
|
||||
ddd: str
|
||||
numero: str
|
||||
|
||||
class ResponsavelTecnico(BaseModel):
|
||||
cpfCnpj: str
|
||||
nome: str
|
||||
email: str
|
||||
telefone: Telefone
|
||||
|
||||
class NFe(BaseModel):
|
||||
idIntegracao: str
|
||||
presencial: bool
|
||||
consumidorFinal: bool
|
||||
natureza: str
|
||||
emitente: Emitente
|
||||
destinatario: Destinatario
|
||||
itens: List[ItemNFe]
|
||||
pagamentos: List[Pagamento]
|
||||
responsavelTecnico: Optional[ResponsavelTecnico] = None
|
||||
49
Models/nfse.py
Normal file
49
Models/nfse.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
class Prestador(BaseModel):
|
||||
cpfCnpj: str
|
||||
|
||||
class Endereco(BaseModel):
|
||||
descricaoCidade: str
|
||||
cep: str
|
||||
tipoLogradouro: str
|
||||
logradouro: str
|
||||
tipoBairro: Optional[str] = None
|
||||
codigoCidade: str
|
||||
complemento: Optional[str] = None
|
||||
estado: str
|
||||
numero: str
|
||||
bairro: str
|
||||
|
||||
class Tomador(BaseModel):
|
||||
cpfCnpj: str
|
||||
razaoSocial: str
|
||||
inscricaoMunicipal: Optional[str] = None
|
||||
email: str
|
||||
endereco: dict
|
||||
|
||||
class ISS(BaseModel):
|
||||
tipoTributacao: int
|
||||
exigibilidade: int
|
||||
aliquota: float
|
||||
|
||||
class Valor(BaseModel):
|
||||
servico: float
|
||||
descontoCondicionado: Optional[float] = 0
|
||||
descontoIncondicionado: Optional[float] = 0
|
||||
|
||||
class Servico(BaseModel):
|
||||
codigo: str
|
||||
codigoTributacao: str
|
||||
discriminacao: str
|
||||
cnae: str
|
||||
iss: ISS
|
||||
valor: Valor
|
||||
|
||||
# Estrutura padrão de envio de uma NFSe contendo Prestador, Tomador e Serviço prestado
|
||||
class NFSe(BaseModel):
|
||||
idIntegracao: str
|
||||
prestador: Prestador
|
||||
tomador: Tomador
|
||||
servico: Servico
|
||||
518
Models/payload.py
Normal file
518
Models/payload.py
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
from enum import Enum
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from Models.config import Config
|
||||
from Models.empresa import Empresa
|
||||
from Models.nfe import NFe
|
||||
from Models.nfse import NFSe
|
||||
from Models.servico import Servico
|
||||
from Models.tomador import Tomador
|
||||
|
||||
|
||||
class Fila(str, Enum):
|
||||
NFE = "nfe"
|
||||
NFSE = "nfse"
|
||||
CERTIFICADO_DIGITAL = "certificado_digital"
|
||||
EMPRESA = "empresa"
|
||||
SERVICO = "servico"
|
||||
TOMADOR = "tomador"
|
||||
RELATORIOS = "relatorios"
|
||||
WEBHOOKS = "webhooks"
|
||||
|
||||
|
||||
class NFeMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
DOWNLOAD = "download"
|
||||
DOWNLOAD_XML = "download_xml"
|
||||
CANCELAR = "cancelar"
|
||||
STATUS_CANCELAMENTO = "status_cancelamento"
|
||||
CCE = "cce"
|
||||
STATUS_CCE = "status_cce"
|
||||
RESUMO = "resumo"
|
||||
|
||||
|
||||
class NFSeMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
DOWNLOAD = "download"
|
||||
DOWNLOAD_XML = "download_xml"
|
||||
CANCELAR = "cancelar"
|
||||
STATUS_CANCELAMENTO = "status_cancelamento"
|
||||
RESUMO = "resumo"
|
||||
HISTORICO = "historico"
|
||||
|
||||
|
||||
class CertificadoMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
PUT = "put"
|
||||
|
||||
|
||||
class EmpresaMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
POST_FILE = "post_file"
|
||||
PATCH = "patch"
|
||||
|
||||
|
||||
class ServicoMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
PUT = "put"
|
||||
DELETE = "delete"
|
||||
|
||||
|
||||
class TomadorMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
PUT = "put"
|
||||
DELETE = "delete"
|
||||
|
||||
|
||||
class RelatorioMetodo(str, Enum):
|
||||
NFE_EMITIDAS = "nfe_emitidas"
|
||||
NFE_POR_CNPJ = "nfe_por_cnpj"
|
||||
NFSE_EMITIDAS = "nfse_emitidas"
|
||||
NFSE_POR_CNPJ = "nfse_por_cnpj"
|
||||
|
||||
|
||||
class WebhookMetodo(str, Enum):
|
||||
GET = "get"
|
||||
POST = "post"
|
||||
DELETE = "delete"
|
||||
PUT = "put"
|
||||
TEST = "test"
|
||||
|
||||
|
||||
class BaseEnvelope(BaseModel):
|
||||
cliente: str | None = None
|
||||
config: Config
|
||||
url_base: str | None = None
|
||||
|
||||
|
||||
# --- Base Data Models ---
|
||||
|
||||
class BaseDados(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class NFePostDadosWrapped(BaseDados):
|
||||
nfe: NFe
|
||||
id_empresa: str | None = None
|
||||
|
||||
|
||||
class NFePostDadosDirect(NFe):
|
||||
id_usuario: int | None = 1
|
||||
id_empresa: str | None = None
|
||||
|
||||
|
||||
class NFeIdDados(BaseDados):
|
||||
id: str
|
||||
|
||||
|
||||
class NFeCancelarDados(NFeIdDados):
|
||||
justificativa: str | None = None
|
||||
|
||||
|
||||
class NFeCCEDados(NFeIdDados):
|
||||
correcao: str
|
||||
|
||||
|
||||
class NFeResumoDados(BaseDados):
|
||||
cnpj: str
|
||||
id_integracao: str | None = None
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class NFSePostDados(NFSe):
|
||||
id_usuario: int | None = 1
|
||||
id_empresa: str | None = None
|
||||
|
||||
|
||||
class NFSeIdDados(BaseDados):
|
||||
id: str
|
||||
|
||||
|
||||
class NFSeGetDados(BaseDados):
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class NFSeResumoDados(NFSeIdDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class NFSeHistoricoDados(NFSeIdDados):
|
||||
cnpj: str | None = None
|
||||
|
||||
|
||||
class CertificadoPostDados(BaseDados):
|
||||
arquivo: str
|
||||
senha: str
|
||||
id_empresa: str | None = None
|
||||
email: str | None = None
|
||||
expiration_at: str | None = Field(None, alias="expirationAt")
|
||||
data_expiracao: str | None = None
|
||||
|
||||
|
||||
class CertificadoGetDados(BaseDados):
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class CertificadoPutDados(CertificadoPostDados):
|
||||
id: str
|
||||
|
||||
|
||||
class EmpresaPostDados(Empresa):
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class EmpresaGetDados(BaseDados):
|
||||
cnpj: str | None = None
|
||||
|
||||
|
||||
class EmpresaPostFileDados(BaseDados):
|
||||
file: str
|
||||
cnpj: str
|
||||
|
||||
|
||||
class EmpresaPatchDados(BaseDados):
|
||||
cnpj: str
|
||||
certificado: str
|
||||
|
||||
|
||||
class ServicoPostDados(Servico):
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class ServicoGetDados(BaseDados):
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class ServicoPutDados(Servico):
|
||||
id: str
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class ServicoDeleteDados(BaseDados):
|
||||
id: str
|
||||
|
||||
|
||||
class TomadorPostDados(Tomador):
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class TomadorGetDados(BaseDados):
|
||||
cpf_cnpj: str | None = None
|
||||
|
||||
|
||||
class TomadorPutDados(Tomador):
|
||||
cpf_cnpj: str
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class TomadorDeleteDados(BaseDados):
|
||||
cpf_cnpj: str
|
||||
|
||||
|
||||
class RelatorioVazioDados(BaseDados):
|
||||
pass
|
||||
|
||||
|
||||
class RelatorioCnpjDados(BaseDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class RelatorioPeriodoDados(BaseDados):
|
||||
data_inicial: str | None = None
|
||||
data_final: str | None = None
|
||||
|
||||
|
||||
class RelatorioCnpjPeriodoDados(RelatorioPeriodoDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class WebhookGetDeleteTestDados(BaseDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class WebhookPostPutDados(WebhookGetDeleteTestDados):
|
||||
url: str
|
||||
|
||||
|
||||
# --- Envelopes ---
|
||||
|
||||
class NFePostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.POST
|
||||
dados: NFePostDadosDirect | NFePostDadosWrapped
|
||||
|
||||
|
||||
class NFeIdEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo
|
||||
dados: NFeIdDados
|
||||
|
||||
|
||||
class NFeCancelarEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.CANCELAR
|
||||
dados: NFeCancelarDados
|
||||
|
||||
|
||||
class NFeCCEEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.CCE
|
||||
dados: NFeCCEDados
|
||||
|
||||
|
||||
class NFeResumoEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.RESUMO
|
||||
dados: NFeResumoDados
|
||||
|
||||
|
||||
class NFSePostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.POST
|
||||
dados: NFSePostDados
|
||||
|
||||
|
||||
class NFSeGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.GET
|
||||
dados: NFSeGetDados
|
||||
|
||||
|
||||
class NFSeIdEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo
|
||||
dados: NFSeIdDados
|
||||
|
||||
|
||||
class NFSeResumoEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.RESUMO
|
||||
dados: NFSeResumoDados
|
||||
|
||||
|
||||
class NFSeHistoricoEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.HISTORICO
|
||||
dados: NFSeHistoricoDados
|
||||
|
||||
|
||||
class CertificadoPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.CERTIFICADO_DIGITAL
|
||||
metodo: CertificadoMetodo = CertificadoMetodo.POST
|
||||
dados: CertificadoPostDados
|
||||
|
||||
|
||||
class CertificadoGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.CERTIFICADO_DIGITAL
|
||||
metodo: CertificadoMetodo = CertificadoMetodo.GET
|
||||
dados: CertificadoGetDados
|
||||
|
||||
|
||||
class CertificadoPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.CERTIFICADO_DIGITAL
|
||||
metodo: CertificadoMetodo = CertificadoMetodo.PUT
|
||||
dados: CertificadoPutDados
|
||||
|
||||
|
||||
class EmpresaPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.POST
|
||||
dados: EmpresaPostDados
|
||||
|
||||
|
||||
class EmpresaGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.GET
|
||||
dados: EmpresaGetDados
|
||||
|
||||
|
||||
class EmpresaPostFileEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.POST_FILE
|
||||
dados: EmpresaPostFileDados
|
||||
|
||||
|
||||
class EmpresaPatchEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.PATCH
|
||||
dados: EmpresaPatchDados
|
||||
|
||||
|
||||
class ServicoPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.POST
|
||||
dados: ServicoPostDados
|
||||
|
||||
|
||||
class ServicoGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.GET
|
||||
dados: ServicoGetDados
|
||||
|
||||
|
||||
class ServicoPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.PUT
|
||||
dados: ServicoPutDados
|
||||
|
||||
|
||||
class ServicoDeleteEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.DELETE
|
||||
dados: ServicoDeleteDados
|
||||
|
||||
|
||||
class TomadorPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.POST
|
||||
dados: TomadorPostDados
|
||||
|
||||
|
||||
class TomadorGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.GET
|
||||
dados: TomadorGetDados
|
||||
|
||||
|
||||
class TomadorPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.PUT
|
||||
dados: TomadorPutDados
|
||||
|
||||
|
||||
class TomadorDeleteEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.DELETE
|
||||
dados: TomadorDeleteDados
|
||||
|
||||
|
||||
class RelatorioNFeEmitidasEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFE_EMITIDAS
|
||||
dados: RelatorioVazioDados
|
||||
|
||||
|
||||
class RelatorioNFeCnpjEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFE_POR_CNPJ
|
||||
dados: RelatorioCnpjDados
|
||||
|
||||
|
||||
class RelatorioNFSeEmitidasEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFSE_EMITIDAS
|
||||
dados: RelatorioPeriodoDados
|
||||
|
||||
|
||||
class RelatorioNFSeCnpjEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFSE_POR_CNPJ
|
||||
dados: RelatorioCnpjPeriodoDados
|
||||
|
||||
|
||||
class WebhookGetDeleteTestEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.WEBHOOKS
|
||||
metodo: WebhookMetodo
|
||||
dados: WebhookGetDeleteTestDados
|
||||
|
||||
|
||||
class WebhookPostPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.WEBHOOKS
|
||||
metodo: WebhookMetodo
|
||||
dados: WebhookPostPutDados
|
||||
|
||||
|
||||
# --- Generic Envelope for basic structural validation ---
|
||||
|
||||
class GenericEnvelope(BaseEnvelope):
|
||||
"""Validates that minimum required fields exist before routing."""
|
||||
fila: Fila
|
||||
metodo: str
|
||||
dados: dict | None = None
|
||||
|
||||
|
||||
# --- Payload Validation Router ---
|
||||
|
||||
# Maps (fila, metodo) -> Envelope class
|
||||
_ENVELOPE_MAP: dict[tuple[str, str], type[BaseEnvelope]] = {
|
||||
# NFe
|
||||
("nfe", "post"): NFePostEnvelope,
|
||||
("nfe", "get"): NFeIdEnvelope,
|
||||
("nfe", "download"): NFeIdEnvelope,
|
||||
("nfe", "download_xml"): NFeIdEnvelope,
|
||||
("nfe", "cancelar"): NFeCancelarEnvelope,
|
||||
("nfe", "status_cancelamento"): NFeIdEnvelope,
|
||||
("nfe", "cce"): NFeCCEEnvelope,
|
||||
("nfe", "status_cce"): NFeIdEnvelope,
|
||||
("nfe", "resumo"): NFeResumoEnvelope,
|
||||
# NFSe
|
||||
("nfse", "post"): NFSePostEnvelope,
|
||||
("nfse", "get"): NFSeGetEnvelope,
|
||||
("nfse", "download"): NFSeIdEnvelope,
|
||||
("nfse", "download_xml"): NFSeIdEnvelope,
|
||||
("nfse", "cancelar"): NFSeIdEnvelope,
|
||||
("nfse", "status_cancelamento"): NFSeIdEnvelope,
|
||||
("nfse", "resumo"): NFSeResumoEnvelope,
|
||||
("nfse", "historico"): NFSeHistoricoEnvelope,
|
||||
# Certificado Digital
|
||||
("certificado_digital", "post"): CertificadoPostEnvelope,
|
||||
("certificado_digital", "get"): CertificadoGetEnvelope,
|
||||
("certificado_digital", "put"): CertificadoPutEnvelope,
|
||||
# Empresa
|
||||
("empresa", "post"): EmpresaPostEnvelope,
|
||||
("empresa", "get"): EmpresaGetEnvelope,
|
||||
("empresa", "post_file"): EmpresaPostFileEnvelope,
|
||||
("empresa", "patch"): EmpresaPatchEnvelope,
|
||||
# Serviço
|
||||
("servico", "post"): ServicoPostEnvelope,
|
||||
("servico", "get"): ServicoGetEnvelope,
|
||||
("servico", "put"): ServicoPutEnvelope,
|
||||
("servico", "delete"): ServicoDeleteEnvelope,
|
||||
# Tomador
|
||||
("tomador", "post"): TomadorPostEnvelope,
|
||||
("tomador", "get"): TomadorGetEnvelope,
|
||||
("tomador", "put"): TomadorPutEnvelope,
|
||||
("tomador", "delete"): TomadorDeleteEnvelope,
|
||||
# Relatórios
|
||||
("relatorios", "nfe_emitidas"): RelatorioNFeEmitidasEnvelope,
|
||||
("relatorios", "nfe_por_cnpj"): RelatorioNFeCnpjEnvelope,
|
||||
("relatorios", "nfse_emitidas"): RelatorioNFSeEmitidasEnvelope,
|
||||
("relatorios", "nfse_por_cnpj"): RelatorioNFSeCnpjEnvelope,
|
||||
# Webhooks
|
||||
("webhooks", "get"): WebhookGetDeleteTestEnvelope,
|
||||
("webhooks", "post"): WebhookPostPutEnvelope,
|
||||
("webhooks", "delete"): WebhookGetDeleteTestEnvelope,
|
||||
("webhooks", "put"): WebhookPostPutEnvelope,
|
||||
("webhooks", "test"): WebhookGetDeleteTestEnvelope,
|
||||
}
|
||||
|
||||
|
||||
def validate_payload(payload_dict: dict) -> BaseEnvelope:
|
||||
# Step 1: Basic structural validation
|
||||
generic = GenericEnvelope(**payload_dict)
|
||||
|
||||
fila = generic.fila.value
|
||||
metodo = generic.metodo.lower()
|
||||
|
||||
# Step 2: Find specific envelope
|
||||
key = (fila, metodo)
|
||||
envelope_cls = _ENVELOPE_MAP.get(key)
|
||||
|
||||
if envelope_cls is None:
|
||||
raise ValueError(f"Combinação fila/metodo desconhecida: {fila}/{metodo}")
|
||||
|
||||
# Step 3: Normalize metodo in the dict to match enum lowercase values
|
||||
payload_dict['metodo'] = metodo
|
||||
|
||||
# Step 4: Full typed validation
|
||||
return envelope_cls(**payload_dict)
|
||||
|
||||
|
||||
|
||||
150
Models/servico.py
Normal file
150
Models/servico.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
from pydantic import BaseModel
|
||||
from enum import IntEnum
|
||||
|
||||
class TipoTributacao(IntEnum):
|
||||
NAO_DEFINIDO = 0
|
||||
ISENTO_ISS = 1
|
||||
IMUNE = 2
|
||||
NAO_INCIDENCIA = 3
|
||||
NAO_TRIBUTAVEL = 4
|
||||
RETIDO = 5
|
||||
TRIBUTAVEL_MUNICIPIO = 6
|
||||
TRIBUTAVEL_FORA_MUNICIPIO = 7
|
||||
TRIBUTAVEL_MUNICIPIO_TOMADOR = 8
|
||||
|
||||
class TipoExigibilidade(IntEnum):
|
||||
EXIGIVEL = 1
|
||||
NAO_INCIDENCIA = 2
|
||||
ISENCAO = 3
|
||||
EXPORTACAO = 4
|
||||
IMUNIDADE = 5
|
||||
SUSPENSO_ACAO_JUDICIAL = 6
|
||||
SUSPENSO_ACAO_ADMINISTRATIVA = 7
|
||||
|
||||
class ISS(BaseModel):
|
||||
tipoTributacao: int #TipoTributacao
|
||||
exigibilidade: int #TipoExigibilidade
|
||||
retido: bool | None = None # Se o ISS foi retido na fonte
|
||||
aliquota: float # Aliquota do ISS
|
||||
valor: float # Valor do ISS
|
||||
valorRetido: float # Valor do ISS retido
|
||||
processoSuspensao: str # Numero processo de suspensão do ISS
|
||||
##############################################################################
|
||||
# tipoTributacao integer
|
||||
# Default: 6
|
||||
# Enum: 0 1 2 3 4 5 6 7 8
|
||||
# Tipo de Tributação do Serviço, valores:
|
||||
# 0 - Não definido
|
||||
# 1 - Isento de ISS
|
||||
# 2 - Imune
|
||||
# 3 - Não Incidência no Município
|
||||
# 4 - Não Tributável
|
||||
# 5 - Retido
|
||||
# 6 - Tributável Dentro do Município
|
||||
# 7 - Tributável Fora do Município
|
||||
# 8 - Tributável Dentro do Município pelo tomador
|
||||
##############################################################################
|
||||
# exigibilidade integer
|
||||
# Default: 1
|
||||
# Enum: 1 2 3 4 5 6 7
|
||||
# Exigibilidade do ISS, valores:
|
||||
# 1 - Exigível
|
||||
# 2 - Não Incidência
|
||||
# 3 - Isenção
|
||||
# 4 - Exportação
|
||||
# 5 - Imunidade
|
||||
# 6 - Supenso por Ação Judicial
|
||||
# 7 - Suspenso por Ação Administrativa
|
||||
##############################################################################
|
||||
# responsavelRetencao string
|
||||
# Valores aceitos:
|
||||
# 1 - Tomador
|
||||
# 2 - Intermediário
|
||||
##############################################################################
|
||||
|
||||
class Obra(BaseModel):
|
||||
art: str # Anotação de Responsabilidade Técnica (Codigo ART)
|
||||
codigo: str # Código da obra
|
||||
|
||||
class Valor(BaseModel):
|
||||
servico: float # Valor bruto do serviço prestado.
|
||||
baseCalculo: float # Valor da base de cálculo dos impostos.
|
||||
deducoes: float # Valor deduções.
|
||||
descontoCondicionado: float # Valor do desconto condicionado.
|
||||
descontoIncondicionado: float # Valor do desconto incondicionado.
|
||||
liquido: float # Valor liquido do serviço, caso você não informe esse campo, vamos calcular automáticamente.
|
||||
unitario: float # Valor unitário do serviço.
|
||||
valorAproximadoTributos: float # Valor aproximado dos tributos.
|
||||
|
||||
class Deducao(BaseModel):
|
||||
tipo: int
|
||||
descricao: str # Descrição da dedução
|
||||
##############################################################################
|
||||
# tipo integer
|
||||
# Tipo de dedução:
|
||||
# 0 - Sem Deduções
|
||||
# 1 - Materiais
|
||||
# 2 - Subempreitada de Mão de Obra
|
||||
# 3 - Serviços
|
||||
# 4 - Produção Externa
|
||||
# 5 - Alimentação e Bebidas
|
||||
# 6 - Reembolso de Despesas
|
||||
# 7 - Repasse Consorciado
|
||||
# 8 - Repasse Plano de Saúde
|
||||
# 99 - Outras Deduções
|
||||
##############################################################################
|
||||
|
||||
class RetencaoInterno(BaseModel):
|
||||
aliquota: float # Aliquota da retenção
|
||||
valor: float # Valor da retenção
|
||||
|
||||
class Retencao(BaseModel):
|
||||
pis: RetencaoInterno
|
||||
cofins: RetencaoInterno
|
||||
csll: RetencaoInterno
|
||||
inss: RetencaoInterno
|
||||
irrf: RetencaoInterno
|
||||
outrasRetencoes: float # Valor de outras retenções
|
||||
cpp: RetencaoInterno
|
||||
|
||||
class Aliquota(BaseModel):
|
||||
municipal: float # Aliquota municipal
|
||||
estadual: float # Aliquota estadual
|
||||
federal: float # Aliquota federal
|
||||
|
||||
class Detalhado(BaseModel):
|
||||
aliquota: Aliquota
|
||||
|
||||
class Simplificado(BaseModel):
|
||||
aliquota: float # Aliquota geral de impostos
|
||||
|
||||
class IBPT(BaseModel):
|
||||
simplificado: Simplificado
|
||||
detalhado: Detalhado
|
||||
|
||||
# Representa o detalhamento do serviço prestado na NFSe, impostos incidentes e deduções
|
||||
class Servico(BaseModel):
|
||||
codigo: str # Código de serviço, também conhecido como Código Item Lista Serviço, geralmente segue a LC116, podendo váriar de acordo com a prefeitura.Você pode encontrar esse dado no portal da prefeitura, em uma nota emitida, ou junto ao contador.
|
||||
idIntegracao: str # Código EXTERNO de integração do serviço. Utilizado SOMENTE quando se cadastra previamente um serviço.
|
||||
discriminacao: str # Descrição do serviço prestado
|
||||
codigoTributacao: str # Código tributação no Município
|
||||
cnae: str # O código CNAE ou, por extenso, Classificação Nacional de Atividades Econômicas, é uma forma de padronizar, em todo o território nacional, os códigos de atividades econômicas e os critérios de enquadramento usados pelos mais diversos órgãos da administração tributária do Brasil.
|
||||
codigoCidadeIncidencia: str # Código IBGE da cidade de incidência do ISS.
|
||||
descricaoCidadeIncidencia: str # Descrição da cidade de incidência do ISS.
|
||||
unidade: str # Unidade do serviço prestado.
|
||||
quantidade: int # Quantidade dos serviços prestados.
|
||||
iss: ISS
|
||||
obra: Obra
|
||||
valor: Valor
|
||||
deducao: Deducao
|
||||
retencao: Retencao
|
||||
tributavel: bool # Indica se o serviço está sujeito a tributação.
|
||||
ibpt: IBPT
|
||||
responsavelRetencao: str
|
||||
|
||||
##############################################################################
|
||||
# responsavelRetencao Valores aceitos:
|
||||
# 1 - Tomador
|
||||
# 2 - Intermediário
|
||||
# Caso o responsável pela retenção seja o próprio prestador, não gerar o campo.
|
||||
##############################################################################
|
||||
34
Models/tomador.py
Normal file
34
Models/tomador.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Endereco(BaseModel):
|
||||
tipoLogradouro: str
|
||||
logradouro: str
|
||||
numero: str
|
||||
complemento: str
|
||||
tipoBairro: str
|
||||
bairro: str
|
||||
codigoPais: str
|
||||
descricaoPais: str
|
||||
codigoCidade: str
|
||||
descricaoCidade: str
|
||||
estado: str
|
||||
cep: str
|
||||
|
||||
class Telefone(BaseModel):
|
||||
ddd: str
|
||||
numero: str
|
||||
|
||||
# Representa o cliente (Tomador do Serviço) vinculado a uma nota fiscal de serviço
|
||||
class Tomador(BaseModel):
|
||||
cpfCnpj: str
|
||||
inscricaoMunicipal: str
|
||||
inscricaoEstadual: str
|
||||
inscricaoSuframa: str
|
||||
indicadorInscricaoEstadual: int
|
||||
razaoSocial: str
|
||||
nomeFantasia: str
|
||||
endereco: Endereco
|
||||
telefone: Telefone
|
||||
email: str
|
||||
orgaoPublico: bool
|
||||
204
README.md
Normal file
204
README.md
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
# Serviço de Integração PlugNotas da Tecnospeed
|
||||
|
||||
Serviço especializado e containerizado para gerenciar a emissão, controle e recebimento de informações sobre **Notas Fiscais (NFe e NFSe)** via API da PlugNotas (Tecnospeed). Com uma arquitetura orientada a eventos e filas (RabbitMQ), o sistema é altamente desacoplado, assíncrono e integrado a um banco de dados PostgreSQL para registro de todas as operações e relatórios de auditoria.
|
||||
|
||||
## 🚀 Principais Tecnologias e Arquitetura
|
||||
|
||||
- **Python & FastAPI**: Base de processamento, recebimento de Webhooks (via `main_listener.py`) e tipagem rigorosa utilizando Pydantic.
|
||||
- **RabbitMQ (AMQP)**: Comunicação entre os módulos utilizando as ferramentas especializadas implementadas (como `QueueExchange2` e `RPCExchange2`), promovendo uma arquitetura event-driven livre de bloqueios síncronos.
|
||||
- **PostgreSQL**: Utilizado com SQLAlchemy para logs, auditorias detalhadas das operações, cache das bases de Serviços, Tomadores, Empresas e relatórios operacionais.
|
||||
- **Redis**: Sistema de gerenciamento e armazenamento de persistência de curadoria (como configurações dinâmicas de acesso e credenciais de forma ágil).
|
||||
- **IC2 Logger**: Envio centralizado de todos os logs, fluxos de erros, métricas operacionais para um sistema `InfoConnector` usando RabbitMQ Topic.
|
||||
|
||||
## 📦 Estrutura de Módulos (Serviços)
|
||||
|
||||
O serviço central funciona consumindo a fila `tecnospeed` e encaminhando as requisições para os seus respectivos módulos especializados.
|
||||
|
||||
### 1. NFe (Nota Fiscal Eletrônica de Produto)
|
||||
Módulo que envia e orquestra operações envolvendo notas fiscais de venda (produtos):
|
||||
- Envio de emissões de NFe em unitário ou em lote.
|
||||
- Consulta de resumos processados.
|
||||
- Cancelamentos suportados por justificativas.
|
||||
- Envio de Cartas de Correção Eletrônica (CCe).
|
||||
- Download de relatórios, XMLs originais e PDFs de Danfe.
|
||||
|
||||
### 2. NFSe (Nota Fiscal de Serviços Eletrônica)
|
||||
Voltado a comunicações com prefeituras para emissão de serviços, contém os fluxos de controle essenciais:
|
||||
- Realiza emissão dos documentos pelo CNPJ do prestador.
|
||||
- Controle e solicitação formal de cancelamento nas prefeituras.
|
||||
- Verificação de status e download de histórico de RPS.
|
||||
- **Tomadores**: Gerenciamento integrado dos clientes finais (Tomadores da NFSe).
|
||||
- **Serviços**: Cadastro de descritivos padrão, itens e alíquotas com base na lei complementar municipal local (LC116).
|
||||
|
||||
### 3. Empresas e Certificados Digitais
|
||||
Serviço de onboarding das novas contas emissoras junto à PlugNotas:
|
||||
- **Empresas**: Vinculação do CNPJ emissor (incluindo endereços, configurações fiscais iniciais e logotipos de impressão).
|
||||
- **Certificados Digitais**: Responsável pelo cadastro do certificado físico formato `.pfx` com envios do tipo `multipart/form-data`, renovação de senhas e update recorrente dos certificados A1.
|
||||
|
||||
### 4. Webhooks (Listeners Internos e Externos)
|
||||
Gerenciador automatizado orientando o lado da PlugNotas para onde devolver as informações assincronamente:
|
||||
- Criação, leitura, deleção e envio de testes para registro das URLs via API do Plugnotas.
|
||||
- Um **Listener próprio do lado da integração** (`main_listener.py` + `listenerEndpoint.py`) absorve o payload recebido da PlugNotas e encaminha de volta ao fluxo RabbitMQ principal usando RPC — mantendo assim uma auditoria segura com log para todas as frentes (rejeições, aceites contábeis etc).
|
||||
|
||||
### 5. Relatórios
|
||||
Serviço voltado a buscas gerenciais dentro do portal da Tecnospeed para contabilidades e analistas de sistemas:
|
||||
- Extratos iterados limitados de NFe e NFSe emitidas ou registradas num dado range de período.
|
||||
- Recortes centralizados e consolidações por um CNPJ específico.
|
||||
|
||||
## ⚙️ Como Funciona o Ciclo de Eventos (Workflows)
|
||||
|
||||
1. **Ação Disparada**: O sistema que integra de fora (ERP etc) publica uma mensagem JSON na fila RabbitMQ com os comandos e a carga de dados de Pydantic Models associada, incluindo as configurações do banco.
|
||||
2. **Workers de Fila (`mainQUEUE.py`)**: As mensagens são absorvidas, descriptografadas, instanciam as credenciais adequadas e ativam a Controller/Service (ex: EmpresaController, RegistroNFeService).
|
||||
3. **Persistência no Postgre (`utils/banco.py`)**: A operação feita junto as rotas do portal salva um rastro de log com o `status` ("SUCESSO", "ERRO"). Instalações da NFe e NFSe e Certificados refletem diretamente nesse PostgreSQL.
|
||||
4. **Listener Asincrono de Retorno (`listenerEndpoint.py`)**: A rota em FastAPI escuta atualizações atrasadas (ex: webservices estaduais demorados) e sinaliza ativamente ao logger e à base da empresa o desfecho da nota.
|
||||
|
||||
## 🗂️ Estrutura de Diretórios
|
||||
|
||||
```
|
||||
servico_plugnotas/
|
||||
├── src/
|
||||
│ ├── main.py # Worker principal (RPCExchange2)
|
||||
│ ├── mainQUEUE.py # Worker alternativo (QueueExchange2 + IC2 Logger)
|
||||
│ ├── main_listener.py # Entrypoint do container de Webhook Listener
|
||||
│ ├── listenerEndpoint.py # Rotas FastAPI que recebem webhooks da PlugNotas
|
||||
│ ├── test.py # Script de testes manuais
|
||||
│ │
|
||||
│ ├── NFe/
|
||||
│ │ └── registroNFe.py # Service de emissão, cancelamento e download de NFe
|
||||
│ ├── NFSe/
|
||||
│ │ ├── nfse.py # Service de emissão e controle de NFSe
|
||||
│ │ ├── Servico/
|
||||
│ │ │ └── servico.py # Controller de cadastro de Serviços (LC116)
|
||||
│ │ └── Tomador/
|
||||
│ │ └── tomador.py # Controller de cadastro de Tomadores
|
||||
│ ├── empresas/
|
||||
│ │ └── empresa.py # Controller de cadastro de Empresas emissoras
|
||||
│ ├── certificado_digital/
|
||||
│ │ └── certificado_digital.py # Service de upload e gestão de certificados .pfx
|
||||
│ ├── webhooks/
|
||||
│ │ └── webhooks.py # CRUD de URLs de webhook por CNPJ
|
||||
│ ├── Relatorios/
|
||||
│ │ └── relatorios.py # Consultas de relatórios em lote
|
||||
│ │
|
||||
│ ├── Models/ # Modelos Pydantic que validam os payloads
|
||||
│ │ ├── config.py # Config (banco) e SystemConfig (AMQP/Redis)
|
||||
│ │ ├── empresa.py # Modelo de Empresa emissora
|
||||
│ │ ├── nfe.py # Modelo de NFe (Emitente, Destinatário, Itens, Tributos)
|
||||
│ │ ├── nfse.py # Modelo de NFSe (Prestador, Tomador, Serviço)
|
||||
│ │ ├── servico.py # Modelo detalhado de Serviço (ISS, Retenções, IBPT)
|
||||
│ │ └── tomador.py # Modelo de Tomador (cliente fiscal)
|
||||
│ │
|
||||
│ └── utils/ # Classes auxiliares compartilhadas
|
||||
│ ├── api.py # Wrapper HTTP (requests) para a API PlugNotas
|
||||
│ ├── banco.py # ORM SQLAlchemy (tabelas + CRUD + logs)
|
||||
│ ├── redis.py # Cliente Redis para cache de configurações
|
||||
│ ├── Exchange2.py # QueueExchange2, RPCExchange2, LoggerExchange2
|
||||
│ ├── exchangeModels.py # Modelos Pydantic do InfoConnector (IC2)
|
||||
│ └── Logger.py # LogService para logs via mensageria
|
||||
│
|
||||
├── Dockerfile # Imagem base Python 3.12-slim
|
||||
├── docker-compose.yml # Orquestração dos containers
|
||||
├── buildImages.sh # Script de build com clone de dependências
|
||||
├── requirements.txt # Dependências Python
|
||||
├── modelo_system_config.json # Exemplo de configuração do Redis
|
||||
└── modelo_json_rpc.json # Exemplo de payload RPC
|
||||
```
|
||||
|
||||
## 🐳 Infraestrutura Docker
|
||||
|
||||
O projeto é composto por **4 containers** orquestrados via `docker-compose.yml`:
|
||||
|
||||
| Container | Imagem | Porta | Função |
|
||||
|----------------------|--------------------|---------|------------------------------------------------------------|
|
||||
| `redis` | `redis` | interna | Armazena `system_config` (credenciais AMQP, chaves) |
|
||||
| `postgres` | `postgres:15` | interna | Banco de dados de auditoria e registros de notas |
|
||||
| `tecnospeed` | build local | — | Worker que consome fila RabbitMQ e processa operações |
|
||||
| `webhook-listener` | build local | `8000` | FastAPI que recebe webhooks da PlugNotas via HTTP |
|
||||
|
||||
### Subindo o ambiente
|
||||
|
||||
```bash
|
||||
# Build e execução completa
|
||||
docker compose up -d --build
|
||||
|
||||
# Ou utilizando o script de build (clona dependências externas automaticamente)
|
||||
bash buildImages.sh clone
|
||||
```
|
||||
|
||||
### Variáveis de ambiente
|
||||
|
||||
| Variável | Container | Descrição | Default |
|
||||
|-----------------|--------------------|-----------------------------------------|-------------|
|
||||
| `TZ` | todos | Timezone da aplicação | `America/Sao_Paulo` |
|
||||
| `REDIS_HOST` | tecnospeed, webhook-listener | Host do Redis | `localhost` |
|
||||
| `WEBHOOK_PORT` | webhook-listener | Porta do servidor FastAPI | `8000` |
|
||||
|
||||
## ⚡ Configuração Inicial (Redis)
|
||||
|
||||
Antes de subir os workers, é necessário inserir a chave `system_config` no Redis com as credenciais de acesso ao RabbitMQ:
|
||||
|
||||
```json
|
||||
{
|
||||
"serviceid": "01234567890123456789012345678901",
|
||||
"servicekey": "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
|
||||
"origin": "nome-da-maquina",
|
||||
"amqps": "amqps://usuario:senha@host.rmq.cloudamqp.com/vhost"
|
||||
}
|
||||
```
|
||||
|
||||
Esse JSON é lido automaticamente na inicialização dos workers (`main.py`, `mainQUEUE.py`, `listenerEndpoint.py`).
|
||||
|
||||
## 📨 Exemplo de Payload RPC
|
||||
|
||||
As mensagens que chegam pela fila RabbitMQ seguem o seguinte formato (já descriptografado):
|
||||
|
||||
```json
|
||||
{
|
||||
"fila": "nfse",
|
||||
"cliente": "NomeCliente",
|
||||
"metodo": "POST",
|
||||
"dados": { ... },
|
||||
"config": {
|
||||
"api_key": "sua-api-key-plugnotas",
|
||||
"user_postgres": "postgres",
|
||||
"pass_postgres": "postgres",
|
||||
"host_postgres": "localhost",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`fila`**: Seleciona o módulo de destino (`nfe`, `nfse`, `empresa`, `certificado_digital`, `servico`, `tomador`, `webhooks`, `relatorios`, `webhook_callback`).
|
||||
- **`metodo`**: Ação a executar (`post`, `get`, `put`, `delete`, `cancelar`, `download`, etc.).
|
||||
- **`dados`**: Corpo da requisição conforme o Pydantic Model do módulo.
|
||||
- **`config`**: Credenciais isoladas por cliente para a API e o banco PostgreSQL.
|
||||
|
||||
## 📦 Dependências
|
||||
|
||||
```
|
||||
sqlalchemy # ORM para PostgreSQL
|
||||
pydantic # Validação de modelos de dados
|
||||
requests # Requisições HTTP à API PlugNotas
|
||||
redis # Cliente Redis
|
||||
pytz # Timezone São Paulo
|
||||
pika # Cliente AMQP (RabbitMQ)
|
||||
cryptography # Criptografia AES-256 dos payloads
|
||||
psycopg2-binary # Driver PostgreSQL
|
||||
fastapi # Framework HTTP para o Webhook Listener
|
||||
uvicorn[standard] # Servidor ASGI
|
||||
```
|
||||
|
||||
## 🔖 Versionamento
|
||||
|
||||
| Versão | Descrição |
|
||||
|---------|--------------------------------------------------|
|
||||
| `1.0.0` | Versão inicial do sistema |
|
||||
| `1.0.1` | Implementação do ORM (SQLAlchemy) |
|
||||
| `1.0.2` | Implementação de RPC via RabbitMQ |
|
||||
| `1.0.3` | Configuração dinâmica via Redis |
|
||||
| `2.0.0` | Suporte a NFe e refatoração da Main |
|
||||
| `2.0.1` | Refatoração para RPCExchange2 / QueueExchange2 |
|
||||
|
||||
---
|
||||
*Documento gerado com base no código operacional contido em `src/` do serviço (v2.0.1).*
|
||||
649
backup/Payloads_Worker_Plugnotas.postman_collection.json
Normal file
649
backup/Payloads_Worker_Plugnotas.postman_collection.json
Normal file
|
|
@ -0,0 +1,649 @@
|
|||
{
|
||||
"info": {
|
||||
"name": "Serviço TecnoSpeed - Payloads do Worker",
|
||||
"description": "Exemplos completos de todos os payloads esperados pelo `main.py` do Worker no RabbitMQ. ATENÇÃO: Essas requisições são modelos do JSON interno. Seu back-end deve encapsula-las com `hmac` e enviá-las ao RabbitMQ.",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "NFe (Nota Fiscal Eletrônica)",
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Cadastrar NFe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfe\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"post\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"nfe\": {\n \"emitente\": {\n \"cpfCnpj\": \"12345678000123\"\n },\n \"destinatario\": {\n \"cpfCnpj\": \"98765432000198\",\n \"razaoSocial\": \"Cliente Destino LTDA\"\n },\n \"itens\": [\n {\n \"codigo\": \"123\",\n \"descricao\": \"Produto Teste\",\n \"valorUnitario\": 100.0,\n \"quantidade\": 1\n }\n ]\n }\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **nfe** usando o método **post**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/nfe/post",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"nfe",
|
||||
"post"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "2. Consultar Resumo NFe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfe\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"get\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"id\": \"ID_DA_NFE_AQUI\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **nfe** usando o método **get**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/nfe/get",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"nfe",
|
||||
"get"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "3. Baixar PDF NFe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfe\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"download\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"id\": \"ID_DA_NFE_AQUI\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **nfe** usando o método **download**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/nfe/download",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"nfe",
|
||||
"download"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "4. Baixar XML NFe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfe\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"download_xml\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"id\": \"ID_DA_NFE_AQUI\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **nfe** usando o método **download_xml**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/nfe/download_xml",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"nfe",
|
||||
"download_xml"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "5. Cancelar NFe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfe\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"cancelar\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"id\": \"ID_DA_NFE_AQUI\",\n \"justificativa\": \"Cancelamento a pedido do integrador\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **nfe** usando o método **cancelar**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/nfe/cancelar",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"nfe",
|
||||
"cancelar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NFSe (Nota Fiscal de Serviço)",
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Cadastrar NFSe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfse\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"post\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"idIntegracao\": \"INTEGRACAO-NFSE-101\",\n \"prestador\": {\n \"cpfCnpj\": \"12345678000123\"\n },\n \"tomador\": {\n \"cpfCnpj\": \"98765432000198\",\n \"razaoSocial\": \"Jo\\u00e3o Silva\",\n \"endereco\": {\n \"logradouro\": \"Rua Teste\",\n \"numero\": \"123\",\n \"codigoCidade\": \"4106902\"\n }\n },\n \"servico\": {\n \"codigo\": \"1.01\",\n \"valor\": 1500.5,\n \"discriminacao\": \"Servi\\u00e7os prestados de TI\"\n }\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **nfse** usando o método **post**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/nfse/post",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"nfse",
|
||||
"post"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "2. Consultar/Listar NFSe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfse\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"get\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"id\": \"ID_DA_NFSE_AQUI\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **nfse** usando o método **get**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/nfse/get",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"nfse",
|
||||
"get"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "3. Cancelar NFSe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfse\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"cancelar\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"id\": \"ID_DA_NFSE_AQUI\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **nfse** usando o método **cancelar**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/nfse/cancelar",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"nfse",
|
||||
"cancelar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Empresa",
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Cadastrar Empresa",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"empresa\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"post\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"json\": {\n \"cpfCnpj\": \"12345678000123\",\n \"razaoSocial\": \"Nome da Sua Empresa LTDA\",\n \"nomeFantasia\": \"Nome Fantasia\",\n \"inscricaoMunicipal\": \"123456\",\n \"endereco\": {\n \"logradouro\": \"Rua Exemplo\",\n \"numero\": \"100\",\n \"codigoCidade\": \"4106902\",\n \"uf\": \"PR\",\n \"cep\": \"80000000\"\n }\n },\n \"info\": {\n \"ativo\": \"true\"\n }\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **empresa** usando o método **post**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/empresa/post",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"empresa",
|
||||
"post"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "2. Consultar Empresa",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"empresa\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"get\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"cnpj\": \"12345678000123\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **empresa** usando o método **get**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/empresa/get",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"empresa",
|
||||
"get"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Certificado Digital",
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Cadastrar Certificado",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"certificado_digital\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"post\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"arquivo\": {\n \"filename\": \"certificado.pfx\",\n \"content\": \"BASE64_CONTEUDO_DO_ARQUIVO\"\n },\n \"senha\": \"SenhaDoCertificadoAqui\",\n \"data_expiracao\": \"2025-12-31\",\n \"id_empresa\": 105,\n \"id_usuario\": 10\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **certificado_digital** usando o método **post**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/certificado_digital/post",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"certificado_digital",
|
||||
"post"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "2. Consultar Certificado",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"certificado_digital\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"get\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"id\": \"ID_DO_CERTIFICADO\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **certificado_digital** usando o método **get**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/certificado_digital/get",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"certificado_digital",
|
||||
"get"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "3. Atualizar Certificado",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"certificado_digital\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"put\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"id\": \"ID_DO_CERTIFICADO\",\n \"arquivo\": {\n \"filename\": \"novo_certificado.pfx\",\n \"content\": \"NEW_BASE64_CONTEUDO_DO_ARQUIVO\"\n },\n \"senha\": \"NovaSenhaDoCertificadoAqui\",\n \"data_expiracao\": \"2026-12-31\",\n \"id_empresa\": 105,\n \"id_usuario\": 10\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **certificado_digital** usando o método **put**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/certificado_digital/put",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"certificado_digital",
|
||||
"put"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Serviços",
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Cadastrar Serviço",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"servico\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"post\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"json\": {\n \"codigo\": \"1.01\",\n \"idIntegracao\": \"SERV-01\",\n \"descricao\": \"An\\u00e1lise e desenvolvimento de sistemas\",\n \"aliquota\": 2.5\n },\n \"info\": {\n \"parametros_adicionais\": \"teste\"\n }\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **servico** usando o método **post**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/servico/post",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"servico",
|
||||
"post"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "2. Buscar Serviço",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"servico\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"get\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"id\": \"ID_DO_SERVICO\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **servico** usando o método **get**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/servico/get",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"servico",
|
||||
"get"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Tomador",
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Cadastrar Tomador",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"tomador\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"post\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"cpf_cnpj\": \"00000000000\",\n \"razaoSocial\": \"Tomador Teste SA\",\n \"email\": \"contato@teste.com\",\n \"endereco\": {\n \"logradouro\": \"Rua teste\",\n \"codigoCidade\": \"1234567\"\n }\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **tomador** usando o método **post**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/tomador/post",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"tomador",
|
||||
"post"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "2. Buscar Tomador",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"tomador\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"get\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"cpf_cnpj\": \"00000000000\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **tomador** usando o método **get**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/tomador/get",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"tomador",
|
||||
"get"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Relatórios",
|
||||
"item": [
|
||||
{
|
||||
"name": "1. NFSe Emitidas",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"relatorios\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"nfse_emitidas\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"data_inicial\": \"01/01/2026\",\n \"data_final\": \"31/01/2026\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **relatorios** usando o método **nfse_emitidas**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/relatorios/nfse_emitidas",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"relatorios",
|
||||
"nfse_emitidas"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "2. NFSe por CNPJ",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"relatorios\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"nfse_por_cnpj\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"cnpj\": \"12345678000123\",\n \"data_inicial\": \"01/01/2026\",\n \"data_final\": \"31/01/2026\"\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **relatorios** usando o método **nfse_por_cnpj**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/relatorios/nfse_por_cnpj",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"relatorios",
|
||||
"nfse_por_cnpj"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Webhook Callbacks (Uso Interno do FastAPI)",
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Notificação Concluído",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"webhook_callback\",\n \"cliente\": \"IdentificadorDoCliente\",\n \"metodo\": \"concluido\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"SUA_API_KEY\",\n \"user_postgres\": \"postgres\",\n \"pass_postgres\": \"postgres\",\n \"host_postgres\": \"localhost\",\n \"port_postgres\": \"5432\",\n \"database_postgres\": \"seu_banco\"\n },\n \"dados\": {\n \"documento\": \"nfe\",\n \"status\": \"CONCLUIDO\",\n \"id_nota\": \"ID_DA_NFE\",\n \"id_integracao\": \"SEU_SISTEMA_123\",\n \"cnpj\": \"12345678000123\",\n \"payload_plugnotas\": {\n \"id\": \"ID_DA_NFE\",\n \"status\": \"CONCLUIDO\",\n \"mensagem\": \"Autorizado o uso da NFe\"\n }\n }\n}"
|
||||
},
|
||||
"description": "Payload interno para a fila **webhook_callback** usando o método **concluido**.\n\n",
|
||||
"url": {
|
||||
"raw": "amqp://seu-rabbitmq/fila/webhook_callback/concluido",
|
||||
"protocol": "amqp",
|
||||
"host": [
|
||||
"seu-rabbitmq"
|
||||
],
|
||||
"path": [
|
||||
"fila",
|
||||
"webhook_callback",
|
||||
"concluido"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
2243
backup/PlugNotas_Example-SandBox.postman_collection.json
Normal file
2243
backup/PlugNotas_Example-SandBox.postman_collection.json
Normal file
File diff suppressed because it is too large
Load diff
8
backup/auxiliar.json
Normal file
8
backup/auxiliar.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"fila": "empresa",
|
||||
"cliente": "Verta",
|
||||
"dados": {
|
||||
"cnpj": "45738057000120",
|
||||
"file": "iVBORw0KGgoAAAANSUhEUgAAA7IAAABlCAYAAABwU9/+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAApDSURBVHhe7d3BS5xnHgfwn5IK7aV/gUUwiMEihtxaCtvjEEgq5GC2XRcvsj1KIEMg22WzhTCB4HXJRZLWxEPAJiBz3IXF3CQSGpRQQdb+Ae0lgbFk9jAzzsw746gTTebpfj4wkHnfJ44Bf/m93+d53te+crlcDgAAAEhEf/YAAAAA9DJBFgAAgKQIsgAAACRFkAUAACApgiwAAABJEWQBAABIiiALAABAUgRZAAAAkiLIAgAAkBRBFgAAgKQIsgAAACRFkAUAACApgiwAAABJEWQBAABIiiALAABAUgRZAAAAkiLIAgAAkBRBFgAAgKQIsgAAACRFkAUAACApgiwAAABJEWQBAABIiiALAABAUgRZAAAAkiLIAgAAkBRBFgAAgKQIsgAAACRFkAUAACApgiwAAABJEWQBAABIiiALAABAUgRZAAAAkiLIAgAAkBRBFgAAgKQIsgAAACRFkAUAACApgiwAAABJEWQBAABIiiALAABAUgRZAAAAkiLIAgAAkBRBFgAAgKQIsgAAACRFkAUAACApgiwAAABJEWQBAABIiiALAABAUgRZAAAAkiLIAgAAkBRBFgAAgKQIsgAAACRFkAUAACApgiwAAABJEWQBAABIiiALAABAUgRZAAAAkiLIAgAAkBRBFgAAgKQIsgAAACRFkAUAACApgiwAAABJEWQBAABIiiALAABAUgRZAAAAktL3888/l7MHAQAAoFf1lctlQRYAAIBk2FoMAABAUgRZAAAAkiLIAgAAkBRBFgAAgKQIsgAAACRFkAUAACApgiwAAABJEWQBAABIiiALAABAUgRZAAAAkiLIAgAAkBRB9i1ZW1vLHgJ6gNqE3qQ2AehEkAUAACApgiwAAABJEWQBAABISl+5XC6/evUqdnd3o1wuZ8/TRl9fX7z33nvx/vvvZ0/ta21tLc6dO5c9DB2pzaNRm7wtavNo1CYAx63v5cuX5devX8epU6eiv98C7WG8fv06fvvtt+jv7z90U9aQOapXr16F2jwatcnboDaPTm0CcNz6d3d3NeMj6u/vj1OnTsXu7m72FBwbtXl0apO3QW0endoE4Lj1l8tlzbgL/f39tpRxotRmd9QmJ01tdkdtAnCcdGIAAACS8rsJssWZgRiYKVberEzHwMB0VN8BAEe0fXs8Bs7Ox3b2ROizALx7PRJkt2P+7EAMDDS/pley44Dfk+JMtu7HY34rO6pVxwts6AnFmG7Tx97lz+67/GwAOG49EmQrRm++iFKpVHltFGJ9ciDGb3fRcs/fi1LpXuSyx4EeUbnIvxiP6jVfKkVpYyYWLrnQBgCgs54Ksk2G5+LZRiHi2oWmFZrt2+P11Zv9ZpYbtjy1m4Fu2oacWQ1uDM6VcfN757sK1UCL7dv5WBorxIuFzHTT8Fw8ezoXQ7X3K9MNq7XVbYxb83Hh2mbE83yMNB6H1GzNx/jAeMzfbvg53+tN9f41X9u5UD23bx+sfb29nlnpb9MrHepmaz7GG3dFNHx+xdP4pqFHZleYG+37fQHACejdIBsRMTwZM2ObsfBDpR1u3x6PkWsT8ai6evNoIh8XDgiXQ1cKMfV8IZb3GnsxHiyORuF6rtrkRyI/UVsVehQTmeAciwsRDyuf9+zK3uU10LXtWP5+M0a/mqwH1nZWpmNgcj0KG5X6e3FzPS7OFCOG5+LxzdGIsUK8KJXsviBxm5H/8fJeD5pavNgcFp/n4+ml6o6FhVzEynRLHxw5TGjcp262f3gaM9UaK20UYrTl89fjbLUHlpanYmmy/cRRN/0ZAN5EbwfZGIrRidqfqxe/N2/sXbTmLk3F5vfLBzTwXFz+sh6GY+VBLI3NxORwRGwtx8LzWqhtMzYi4stCzA3X3wLHY2K0HmOb7pWtXpQXHy411d/QFzMxuvig7UU0pKuxB52Js2OZ02OFuHG+/rZSF5frffB6IUabJmuPZujKvXqPG56MmbGI9c2GHljrlxER5y/HVCzFg5ZV2W77MwB0r8eD7HZsrjcf2bw2Ur/gnVxqPrmPxoZafLiUWQnajPyZ+kX0xcWmvwqckMaL5dxCbdV1tGlMLF6s1/uZfGw2n4U0TYx23o1wgNGPz9TfDI/G3nxvVxpvrxmJ/PPs+UZtgnaDbvozAHSrt4NsdcV05ot6y59abngwTKkUpcb76fZz/nJ1e3ExHixORaFpi/DU3lao2ssW4u6sFnJx60n2KGQNxeRXo4darWl6AJxtxCSlfejb+PHNp2M2f9yov9najMx87xFkb695EYU233PdRjztEHS76s//5/RNgO71bpDdmo/xM/mIm4+r254qF7/N9+dsx/zMIe4NilzcuBmxcCkfSw1bsirbqJYq993VbM3H9Du+r+fC0oVDvTrZuT8buVyu8vp6KXayAzI6j9+Jpa+r53K5mL3f7qutxuq/P49PP8keh1aVe9fzMdLyYJm63KWp2Mzcs16cqdT/0OhExPOn0XA5Dz2mTc/amo/8YsTUpe6nY3KXpiIW83t1Ufw2H5u17b/DozERm/G0lpVXvmlaYd2vbuorvJ2Dau0hbY1bnSva/FsP3Z+Px1/+Pn+oVyed+2CrzuP1TYCT1lNBtmlb0pl8TCw3r44OXXlWeeBLbczASCx8fMADY6qGvpiJeL6ZuYAYirmnL6Kw3rh9cSHONqwAJ+nJrZj97nRcLxajWCzG9aG7MVtYzY6qO2D8amE27g5dj2KxGMXi9Tj93WzrDPKT1fjXHz6NTzOHob1c3CuV4lE01N7AQIx8PxMvaqs45+9FaXmieet/VCeizt+IwthS9f+C9g+fgXdt6MqzePRl7ee03tfutQTBIzh/L17cjL26uLheqNdM5OLe8lQsTVY/7+HZ5hXWlroZirlvpxp6bz7Wsyuye085Hqg8zGmfVdY36c894YA+2OKA8fomwMnr++WXX8offPBB9jiH8PLly/jwww+zh9taW1uLc+fOZQ+3ddBqa83jqcfZQxHVrUp3B+/EnT8OVg48uRW5f0RcL15t2zA7j1+NW7m78dGdOzFVPb1ayMW3cT2K+fpXWy3kYvWzYlytzizv3J+N2e/qM9Cf/7V+jsP59ddfQ21256RqE0JtvpGTqs2DVltr/vm3ueyhiAP7YKvO4/VNgLehp1ZkOQ47sZPdyzX4UQzGT7HTbmfTQeN3duKnzOmPBgcjtncatlFltkftLMU/Gmaqi3f+HLHd9sMB4B07oA+2OGC8vgnwVrxZkF2p/BL3Tr8gvae1/PL47LmBGH/H98t26/RQdRr4kDqPPx2DHU7v3L/bvD1q57+x03gBMDgVV2uz1vSo6pNLD/P7KHtFp/qF35sUelIK32MHnftgq87j9U2Ak/YGQbYY05PrUdh4w/t9Iipfq9cC8fBcPNsoRFz7Jsn773464kxu5/H7zUpHROzE6n924vPPGjZffXI17vwp4u5s7UEXt6LDnUb0gO3bFypPLt3n/jfS5smoqduO+Uutz404krcx8aNvNtA3AU7a/wAJTX+BdYrxlwAAAABJRU5ErkJggg=="
|
||||
}
|
||||
}
|
||||
86
backup/cadastrar_empresa.json
Normal file
86
backup/cadastrar_empresa.json
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"endereco": {
|
||||
"codigoPais": "1058",
|
||||
"descricaoPais": "Brasil",
|
||||
"tipoLogradouro": "Rua",
|
||||
"logradouro": "T55",
|
||||
"numero": "882",
|
||||
"complemento": "Quadra99 Lote 11/14 Sala 1507",
|
||||
"tipoBairro": "Zona",
|
||||
"bairro": "Setor Bueno",
|
||||
"codigoCidade": "5208707",
|
||||
"descricaoCidade": "Goiania",
|
||||
"estado": "GO",
|
||||
"cep": "74215170"
|
||||
},
|
||||
"nfse": {
|
||||
"config": {
|
||||
"rps": {
|
||||
"lote": 12,
|
||||
"numeracao": [
|
||||
{
|
||||
"numero": 2,
|
||||
"serie": "CTBZ",
|
||||
"numeracaoAtual": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"email": {
|
||||
"envio": false
|
||||
},
|
||||
"producao": true,
|
||||
"envioLimitado": false,
|
||||
"integracoes": []
|
||||
},
|
||||
"ativo": true,
|
||||
"tipoContrato": 0
|
||||
},
|
||||
"nfe": {
|
||||
"config": {
|
||||
"dfe": {
|
||||
"primeiroNsu": true
|
||||
},
|
||||
"email": {
|
||||
"envio": false
|
||||
},
|
||||
"integracoes": {
|
||||
"plugStorage": {
|
||||
"ativo": false
|
||||
}
|
||||
},
|
||||
"producao": false,
|
||||
"impressaoFcp": false,
|
||||
"impressaoPartilha": true,
|
||||
"versaoManual": "6.0",
|
||||
"versaoEsquema": "pl_009",
|
||||
"numeracao": []
|
||||
},
|
||||
"ativo": false
|
||||
},
|
||||
"nfce": {
|
||||
"config": {
|
||||
"email": {
|
||||
"envio": false
|
||||
},
|
||||
"integracoes": {
|
||||
"plugStorage": {
|
||||
"ativo": false
|
||||
}
|
||||
},
|
||||
"producao": false,
|
||||
"versaoManual": "6.0",
|
||||
"versaoEsquema": "pl_009",
|
||||
"numeracao": []
|
||||
},
|
||||
"ativo": false
|
||||
},
|
||||
"incentivoFiscal": false,
|
||||
"incentivadorCultural": false,
|
||||
"cpfCnpj": "80972076000118",
|
||||
"inscricaoMunicipal": "108532836",
|
||||
"certificado": "668698592698d7831c758d52",
|
||||
"razaoSocial": "Teste",
|
||||
"simplesNacional": true,
|
||||
"regimeTributario": 1,
|
||||
"regimeTributarioEspecial": 6
|
||||
}
|
||||
125
backup/collectionsForEduardo.json
Normal file
125
backup/collectionsForEduardo.json
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
{
|
||||
"info": {
|
||||
"name": "APIs Frontend - Documentação de Payloads",
|
||||
"description": "Estes são os JSONs puros extremamente completos com todas as tags mapeadas nos modelos Pydantic.",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Cadastrar NFe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"nfe\": {\n \"idIntegracao\": \"XXXYY99999_TESTE1\",\n \"presencial\": true,\n \"consumidorFinal\": true,\n \"natureza\": \"OPERAÇÃO INTERNA\",\n \"emitente\": {\n \"cpfCnpj\": \"08187168000160\"\n },\n \"destinatario\": {\n \"cpfCnpj\": \"08114280956\",\n \"razaoSocial\": \"NF-E EMITIDA EM HOMOLOGACAO\",\n \"email\": \"contato@tecnospeed.com.br\",\n \"endereco\": {\n \"logradouro\": \"AVENIDA DUQUE DE CAXIAS\",\n \"numero\": \"882\",\n \"bairro\": \"CENTRO\",\n \"codigoCidade\": \"4115200\",\n \"descricaoCidade\": \"MARINGA\",\n \"estado\": \"PR\",\n \"cep\": \"87020025\"\n }\n },\n \"itens\": [\n {\n \"codigo\": \"1\",\n \"descricao\": \"PRODUTO DE TESTE\",\n \"ncm\": \"06029090\",\n \"cest\": \"0123456\",\n \"cfop\": \"5101\",\n \"valorUnitario\": {\n \"comercial\": 4.67,\n \"tributavel\": 4.67\n },\n \"valor\": 4.67,\n \"tributos\": {\n \"icms\": {\n \"origem\": \"0\",\n \"cst\": \"00\",\n \"baseCalculo\": {\n \"modalidadeDeterminacao\": 0,\n \"valor\": 0\n },\n \"aliquota\": 0,\n \"valor\": 0\n },\n \"pis\": {\n \"cst\": \"99\",\n \"baseCalculo\": {\n \"valor\": 0,\n \"quantidade\": 0\n },\n \"aliquota\": 0,\n \"valor\": 0\n },\n \"cofins\": {\n \"cst\": \"07\",\n \"baseCalculo\": {\n \"valor\": 0\n },\n \"aliquota\": 0,\n \"valor\": 0\n }\n }\n }\n ],\n \"pagamentos\": [\n {\n \"aVista\": true,\n \"meio\": \"01\",\n \"valor\": 4.6\n }\n ],\n \"responsavelTecnico\": {\n \"cpfCnpj\": \"08187168000160\",\n \"nome\": \"Tecnospeed\",\n \"email\": \"contato@tecnospeed.com.br\",\n \"telefone\": {\n \"ddd\": \"44\",\n \"numero\": \"30379500\"\n }\n }\n }\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://api.sandbox.plugnotas.com.br/nfe",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"api.sandbox.plugnotas.com.br/nfe"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2. Cadastrar NFSe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"idIntegracao\": \"INTEGRACAO-NFSE-001\",\n \"prestador\": {\n \"cpfCnpj\": \"12345678000123\"\n },\n \"tomador\": {\n \"cpfCnpj\": \"98765432000198\",\n \"razaoSocial\": \"João Tomador da Silva\",\n \"inscricaoMunicipal\": \"isento\",\n \"email\": \"tomador@email.com\",\n \"endereco\": {\n \"descricaoCidade\": \"São Paulo\",\n \"cep\": \"00000000\",\n \"tipoLogradouro\": \"Rua\",\n \"logradouro\": \"Dois de Ouro\",\n \"tipoBairro\": \"Bairro\",\n \"codigoCidade\": \"3550308\",\n \"complemento\": \"Apto 13\",\n \"estado\": \"SP\",\n \"numero\": \"44\",\n \"bairro\": \"Centro\"\n }\n },\n \"servico\": {\n \"codigo\": \"1.01\",\n \"codigoTributacao\": \"1.01\",\n \"discriminacao\": \"Serviços prestados de tecnologia\",\n \"cnae\": \"6204000\",\n \"iss\": {\n \"tipoTributacao\": 6,\n \"exigibilidade\": 1,\n \"aliquota\": 2.5\n },\n \"valor\": {\n \"servico\": 1500.50,\n \"descontoCondicionado\": 0.0,\n \"descontoIncondicionado\": 0.0\n }\n }\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://api.sandbox.plugnotas.com.br/nfse",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"api.sandbox.plugnotas.com.br/nfse"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "3. Cadastrar Empresa",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"endereco\": {\n \"codigoPais\": \"1058\",\n \"descricaoPais\": \"BRASIL\",\n \"tipoLogradouro\": \"Rua\",\n \"logradouro\": \"Teste\",\n \"numero\": \"123\",\n \"complemento\": \"Terreo\",\n \"tipoBairro\": \"Bairro\",\n \"bairro\": \"Centro\",\n \"codigoCidade\": \"4106902\",\n \"descricaoCidade\": \"CURITIBA\",\n \"estado\": \"PR\",\n \"cep\": \"80000000\"\n },\n \"nfse\": {\n \"config\": {\n \"rps\": {\n \"lote\": 1,\n \"numeracao\": [\n 1\n ]\n },\n \"email\": {\n \"envio\": true\n },\n \"producao\": false,\n \"envioLimitado\": false,\n \"integracoes\": []\n },\n \"ativo\": true,\n \"tipoContrato\": 0\n },\n \"nfe\": {\n \"config\": {\n \"dfe\": {\n \"primeiroNsu\": false\n },\n \"email\": {\n \"envio\": false\n },\n \"integracoes\": {},\n \"producao\": false,\n \"impressaoFcp\": false,\n \"impressaoPartilha\": false,\n \"versaoManual\": \"6.0\",\n \"versaoEsquema\": \"PL_009_V4\",\n \"numeracao\": [\n 1\n ]\n },\n \"ativo\": false\n },\n \"nfce\": {\n \"config\": {\n \"email\": {\n \"envio\": false\n },\n \"integracoes\": {},\n \"producao\": false,\n \"versaoManual\": \"6.0\",\n \"versaoEsquema\": \"PL_009_V4\",\n \"numeracao\": [\n 1\n ]\n },\n \"ativo\": false\n },\n \"incentivoFiscal\": false,\n \"incentivadorCultural\": false,\n \"cpfCnpj\": \"12345678000123\",\n \"inscricaoMunicipal\": \"12345\",\n \"certificado\": \"60b8d5a1-UUID-DO-CERT\",\n \"razaoSocial\": \"Empresa Teste Completa SA\",\n \"simplesNacional\": true,\n \"regimeTributario\": 1,\n \"regimeTributarioEspecial\": 0\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://api.sandbox.plugnotas.com.br/empresa",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"api.sandbox.plugnotas.com.br/empresa"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "4. Cadastrar Certificado Digital",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"arquivo\": \"MIIJ/AIBAzCCC... (BASE64 STRING)\",\n \"senha\": \"minhasenha123\",\n \"id_empresa\": \"Id da Empresa (Ex: 105)\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://api.sandbox.plugnotas.com.br/certificado",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"api.sandbox.plugnotas.com.br/certificado"
|
||||
]
|
||||
},
|
||||
"description": "Payload enxuto como refatorado."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "5. Cadastrar Serviço de NFSe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"codigo\": \"1.01\",\n \"idIntegracao\": \"SRV-101\",\n \"discriminacao\": \"An\\u00e1lise e desenvolvimento de sistemas\",\n \"codigoTributacao\": \"1.01\",\n \"cnae\": \"6204000\",\n \"codigoCidadeIncidencia\": \"3550308\",\n \"descricaoCidadeIncidencia\": \"S\\u00c3O PAULO\",\n \"unidade\": \"UN\",\n \"quantidade\": 1,\n \"iss\": {\n \"tipoTributacao\": 6,\n \"exigibilidade\": 1,\n \"retido\": false,\n \"aliquota\": 2.5,\n \"valor\": 37.5,\n \"valorRetido\": 0.0,\n \"processoSuspensao\": \"\"\n },\n \"obra\": {\n \"art\": \"\",\n \"codigo\": \"\"\n },\n \"valor\": {\n \"servico\": 1500.0,\n \"baseCalculo\": 1500.0,\n \"deducoes\": 0.0,\n \"descontoCondicionado\": 0.0,\n \"descontoIncondicionado\": 0.0,\n \"liquido\": 1500.0,\n \"unitario\": 1500.0,\n \"valorAproximadoTributos\": 0.0\n },\n \"deducao\": {\n \"tipo\": 0,\n \"descricao\": \"\"\n },\n \"retencao\": {\n \"pis\": {\n \"aliquota\": 0,\n \"valor\": 0\n },\n \"cofins\": {\n \"aliquota\": 0,\n \"valor\": 0\n },\n \"csll\": {\n \"aliquota\": 0,\n \"valor\": 0\n },\n \"inss\": {\n \"aliquota\": 0,\n \"valor\": 0\n },\n \"irrf\": {\n \"aliquota\": 0,\n \"valor\": 0\n },\n \"outrasRetencoes\": 0,\n \"cpp\": {\n \"aliquota\": 0,\n \"valor\": 0\n }\n },\n \"tributavel\": true,\n \"ibpt\": {\n \"simplificado\": {\n \"aliquota\": 0\n },\n \"detalhado\": {\n \"aliquota\": {\n \"municipal\": 0.0,\n \"estadual\": 0.0,\n \"federal\": 0.0\n }\n }\n },\n \"responsavelRetencao\": \"1\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "https://api.sandbox.plugnotas.com.br/nfse/servico",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"api.sandbox.plugnotas.com.br/nfse/servico"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
86
backup/json_insercao_empresa.json
Normal file
86
backup/json_insercao_empresa.json
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"endereco": {
|
||||
"codigoPais": "1058",
|
||||
"descricaoPais": "Brasil",
|
||||
"tipoLogradouro": "Rua",
|
||||
"logradouro": "T55",
|
||||
"numero": "882",
|
||||
"complemento": "Quadra99 Lote 11/14 Sala 1507",
|
||||
"tipoBairro": "Zona",
|
||||
"bairro": "Setor Bueno",
|
||||
"codigoCidade": "5208707",
|
||||
"descricaoCidade": "Goiania",
|
||||
"estado": "GO",
|
||||
"cep": "74215170"
|
||||
},
|
||||
"nfse": {
|
||||
"config": {
|
||||
"rps": {
|
||||
"lote": 12,
|
||||
"numeracao": [
|
||||
{
|
||||
"numero": 2,
|
||||
"serie": "CTBZ",
|
||||
"numeracaoAtual": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"email": {
|
||||
"envio": false
|
||||
},
|
||||
"producao": true,
|
||||
"envioLimitado": false,
|
||||
"integracoes": []
|
||||
},
|
||||
"ativo": true,
|
||||
"tipoContrato": 0
|
||||
},
|
||||
"nfe": {
|
||||
"config": {
|
||||
"dfe": {
|
||||
"primeiroNsu": true
|
||||
},
|
||||
"email": {
|
||||
"envio": false
|
||||
},
|
||||
"integracoes": {
|
||||
"plugStorage": {
|
||||
"ativo": false
|
||||
}
|
||||
},
|
||||
"producao": false,
|
||||
"impressaoFcp": false,
|
||||
"impressaoPartilha": true,
|
||||
"versaoManual": "6.0",
|
||||
"versaoEsquema": "pl_009",
|
||||
"numeracao": []
|
||||
},
|
||||
"ativo": false
|
||||
},
|
||||
"nfce": {
|
||||
"config": {
|
||||
"email": {
|
||||
"envio": false
|
||||
},
|
||||
"integracoes": {
|
||||
"plugStorage": {
|
||||
"ativo": false
|
||||
}
|
||||
},
|
||||
"producao": false,
|
||||
"versaoManual": "6.0",
|
||||
"versaoEsquema": "pl_009",
|
||||
"numeracao": []
|
||||
},
|
||||
"ativo": false
|
||||
},
|
||||
"incentivoFiscal": false,
|
||||
"incentivadorCultural": false,
|
||||
"cpfCnpj": "45732212000100",
|
||||
"inscricaoMunicipal": "108532836",
|
||||
"certificado": "668698592698d7831c758d52",
|
||||
"razaoSocial": "Teste",
|
||||
"simplesNacional": true,
|
||||
"regimeTributario": 1,
|
||||
"regimeTributarioEspecial": 6
|
||||
}
|
||||
29
backup/modelo_json_rpc.json
Normal file
29
backup/modelo_json_rpc.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"system": "integracao",
|
||||
"target": "rpcclt",
|
||||
"service": "tecnospeed",
|
||||
"serviceId": "01234567890123456789012345678901",
|
||||
"payload": {
|
||||
"fila": "nfse",
|
||||
"cliente": "Verta",
|
||||
"metodo": "POST",
|
||||
"dados": null,
|
||||
"config":{
|
||||
"api_key": "2da392a6-79d2-4304-a8b7-959572c7e44d",
|
||||
"user_postgres": "postgres",
|
||||
"pass_postgres": "postgres",
|
||||
"host_postgres": "localhost",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
}
|
||||
},
|
||||
"hmac": "38051e9be76496a05a237fdabc19898ef7ab3f469c5873b96a4fdbb5be30b10c",
|
||||
"type": "rpc",
|
||||
"correlationId": "884ea465-e2b6-4eab-bfdf-a08c9fb493d3",
|
||||
"version": "1.0",
|
||||
"origin": "kuririn",
|
||||
"client": "verta",
|
||||
"caller": "callRPC",
|
||||
"logDate": "2025-02-24T16:41:40.882Z",
|
||||
"msg": {"cliente":"Verta","servico":"webhooks"}
|
||||
}
|
||||
6
backup/modelo_system_config.json
Normal file
6
backup/modelo_system_config.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"serviceid":"01234567890123456789012345678901",
|
||||
"servicekey":"ABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
|
||||
"origin":"kuririn",
|
||||
"amqps":"amqps://qyqordbr:5tDIJIX6z5k3qaMmpJGC4HIieqq4n29z@gull.rmq.cloudamqp.com/qyqordbr"
|
||||
}
|
||||
129
backup/servicoPlugnotasCollections.json
Normal file
129
backup/servicoPlugnotasCollections.json
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
{
|
||||
"info": {
|
||||
"_postman_id": "55e2d8ab-7890-4813-89bd-a287c7e44d1a",
|
||||
"name": "Serviço PlugNotas — Messaging (RPC & Queue)",
|
||||
"description": "Collection de mensageria para o Serviço PlugNotas.",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "NFe",
|
||||
"description": "Operações de Nota Fiscal Eletrônica (NFe) — emissão, consulta, download, cancelamento e resumo.",
|
||||
"item": [
|
||||
{
|
||||
"name": "POST - Emitir NFe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfe\",\n \"metodo\": \"post\",\n \"cliente\": \"{{cliente}}\",\n \"url_base\": \"https://api.sandbox.plugnotas.com.br\",\n \"config\": {\n \"api_key\": \"{{api_key}}\",\n \"user_postgres\": \"{{user_postgres}}\",\n \"pass_postgres\": \"{{pass_postgres}}\",\n \"host_postgres\": \"{{host_postgres}}\",\n \"port_postgres\": \"{{port_postgres}}\",\n \"database_postgres\": \"{{database_postgres}}\"\n },\n \"dados\": {\n \"idIntegracao\": \"NFe_TESTE_001\",\n \"presencial\": true,\n \"consumidorFinal\": true,\n \"natureza\": \"OPERAÇÃO INTERNA\",\n \"emitente\": {\n \"cpfCnpj\": \"00000000000000\",\n \"inscricaoEstadual\": \"ISENTO\"\n },\n \"destinatario\": {\n \"cpfCnpj\": \"08114280956\",\n \"razaoSocial\": \"NF-E EMITIDA EM HOMOLOGACAO\",\n \"endereco\": {\n \"logradouro\": \"AVENIDA DUQUE DE CAXIAS\",\n \"numero\": \"882\",\n \"bairro\": \"CENTRO\",\n \"codigoCidade\": \"4115200\",\n \"descricaoCidade\": \"MARINGA\",\n \"estado\": \"PR\",\n \"cep\": \"87020025\"\n }\n },\n \"itens\": [\n {\n \"codigo\": \"1\",\n \"descricao\": \"PRODUTO DE TESTE\",\n \"ncm\": \"06029090\",\n \"cfop\": \"5101\",\n \"valorUnitario\": {\n \"comercial\": 4.67,\n \"tributavel\": 4.67\n },\n \"valor\": 4.67,\n \"tributos\": {\n \"icms\": {\n \"origem\": \"0\",\n \"cst\": \"00\",\n \"baseCalculo\": {\n \"modalidadeDeterminacao\": 0,\n \"valor\": 0\n },\n \"aliquota\": 0,\n \"valor\": 0\n }\n }\n }\n ],\n \"pagamentos\": [\n {\n \"aVista\": true,\n \"meio\": \"01\",\n \"valor\": 4.67\n }\n ]\n }\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {},
|
||||
"description": "Emite uma NFe de acordo com as exigências da PlugNotas.\n\n**Campos obrigatórios em `dados`:** idIntegracao, presencial, consumidorFinal, natureza, emitente (cpfCnpj), destinatario (cpfCnpj, razaoSocial, endereco), itens, pagamentos."
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "GET - Consultar NFe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfe\",\n \"metodo\": \"get\",\n \"cliente\": \"{{cliente}}\",\n \"config\": {\n \"api_key\": \"{{api_key}}\",\n \"user_postgres\": \"{{user_postgres}}\",\n \"pass_postgres\": \"{{pass_postgres}}\",\n \"host_postgres\": \"{{host_postgres}}\",\n \"port_postgres\": \"{{port_postgres}}\",\n \"database_postgres\": \"{{database_postgres}}\"\n },\n \"dados\": {\n \"id\": \"ID_DA_NOTA\"\n }\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {},
|
||||
"description": "Consulta os dados de uma NFe pelo ID."
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NFSe",
|
||||
"description": "Operações de NFSe — emissão, consulta, download, etc.",
|
||||
"item": [
|
||||
{
|
||||
"name": "POST - Emitir NFSe",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"nfse\",\n \"metodo\": \"post\",\n \"cliente\": \"{{cliente}}\",\n \"config\": {\n \"api_key\": \"{{api_key}}\",\n \"user_postgres\": \"{{user_postgres}}\",\n \"pass_postgres\": \"{{pass_postgres}}\",\n \"host_postgres\": \"{{host_postgres}}\",\n \"port_postgres\": \"{{port_postgres}}\",\n \"database_postgres\": \"{{database_postgres}}\"\n },\n \"dados\": {\n \"idIntegracao\": \"NFSe_001\",\n \"prestador\": {\n \"cpfCnpj\": \"00000000000000\"\n },\n \"tomador\": {\n \"cpfCnpj\": \"11111111111\",\n \"razaoSocial\": \"Razao Social do Tomador\",\n \"inscricaoMunicipal\": \"ISENTO\",\n \"email\": \"tomador@exemplo.com\",\n \"endereco\": {\n \"descricaoCidade\": \"Sao Paulo\",\n \"cep\": \"00000000\",\n \"tipoLogradouro\": \"Rua\",\n \"logradouro\": \"Rua de Teste\",\n \"tipoBairro\": \"Bairro\",\n \"codigoCidade\": \"3550308\",\n \"complemento\": \"\",\n \"estado\": \"SP\",\n \"numero\": \"123\",\n \"bairro\": \"Centro\"\n }\n },\n \"servico\": {\n \"codigo\": \"1.01\",\n \"codigoTributacao\": \"1.01.01\",\n \"discriminacao\": \"Descricao do servico\",\n \"cnae\": \"6201100\",\n \"iss\": {\n \"tipoTributacao\": 6,\n \"exigibilidade\": 1,\n \"aliquota\": 2.0\n },\n \"valor\": {\n \"servico\": 150.0,\n \"descontoCondicionado\": 0.0,\n \"descontoIncondicionado\": 0.0\n }\n }\n }\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {},
|
||||
"description": "Emite uma NFSe."
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Empresa",
|
||||
"description": "Cadastro e gerenciamento de empresas.",
|
||||
"item": [
|
||||
{
|
||||
"name": "POST - Cadastro Completo",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"empresa\",\n \"metodo\": \"post\",\n \"cliente\": \"{{cliente}}\",\n \"config\": {\n \"api_key\": \"{{api_key}}\",\n \"user_postgres\": \"{{user_postgres}}\",\n \"pass_postgres\": \"{{pass_postgres}}\",\n \"host_postgres\": \"{{host_postgres}}\",\n \"port_postgres\": \"{{port_postgres}}\",\n \"database_postgres\": \"{{database_postgres}}\"\n },\n \"dados\": {\n \"cpfCnpj\": \"00000000000000\",\n \"razaoSocial\": \"Empresa Teste S/A\",\n \"inscricaoMunicipal\": \"1234\",\n \"inscricaoEstadual\": \"ISENTO\",\n \"simplesNacional\": true,\n \"regimeTributario\": 1,\n \"regimeTributarioEspecial\": 0,\n \"incentivoFiscal\": false,\n \"incentivadorCultural\": false,\n \"certificado\": \"A1\",\n \"endereco\": {\n \"codigoPais\": \"1058\",\n \"descricaoPais\": \"Brasil\",\n \"tipoLogradouro\": \"Rua\",\n \"logradouro\": \"Rua das Industrias\",\n \"numero\": \"1050\",\n \"complemento\": \"Hangar 2\",\n \"tipoBairro\": \"Bairro\",\n \"bairro\": \"Parque Industrial\",\n \"codigoCidade\": \"3550308\",\n \"descricaoCidade\": \"Sao Paulo\",\n \"estado\": \"SP\",\n \"cep\": \"00000000\"\n },\n \"nfse\": {\n \"ativo\": true,\n \"tipoContrato\": 1,\n \"config\": {\n \"rps\": { \"lote\": 1, \"numeracao\": [] },\n \"email\": { \"envio\": true },\n \"producao\": false,\n \"envioLimitado\": false,\n \"integracoes\": [],\n \"prefeitura\": null\n }\n },\n \"nfe\": {\n \"ativo\": true,\n \"config\": {\n \"dfe\": { \"primeiroNsu\": true },\n \"email\": { \"envio\": true },\n \"integracoes\": {},\n \"producao\": false,\n \"impressaoFcp\": false,\n \"impressaoPartilha\": false,\n \"versaoManual\": \"4.0\",\n \"versaoEsquema\": \"4.00\",\n \"numeracao\": []\n }\n }\n }\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {},
|
||||
"description": "Cadastro de empresa com NFe e NFSe."
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Tomador",
|
||||
"description": "Cadastro de tomadores/clientes.",
|
||||
"item": [
|
||||
{
|
||||
"name": "POST - Criar Tomador",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"fila\": \"tomador\",\n \"metodo\": \"post\",\n \"cliente\": \"{{cliente}}\",\n \"config\": {\n \"api_key\": \"{{api_key}}\",\n \"user_postgres\": \"{{user_postgres}}\",\n \"pass_postgres\": \"{{pass_postgres}}\",\n \"host_postgres\": \"{{host_postgres}}\",\n \"port_postgres\": \"{{port_postgres}}\",\n \"database_postgres\": \"{{database_postgres}}\"\n },\n \"dados\": {\n \"cpfCnpj\": \"12345678901\",\n \"inscricaoMunicipal\": \"ISENTO\",\n \"inscricaoEstadual\": \"ISENTO\",\n \"inscricaoSuframa\": \"\",\n \"indicadorInscricaoEstadual\": 9,\n \"razaoSocial\": \"Cliete Teste\",\n \"nomeFantasia\": \"Empresa Teste\",\n \"email\": \"tomador@exemplo.com\",\n \"orgaoPublico\": false,\n \"endereco\": {\n \"tipoLogradouro\": \"Rua\",\n \"logradouro\": \"Rua de Teste\",\n \"numero\": \"100\",\n \"complemento\": \"\",\n \"tipoBairro\": \"Bairro\",\n \"bairro\": \"Centro\",\n \"codigoPais\": \"1058\",\n \"descricaoPais\": \"Brasil\",\n \"codigoCidade\": \"3550308\",\n \"descricaoCidade\": \"Sao Paulo\",\n \"estado\": \"SP\",\n \"cep\": \"00000000\"\n },\n \"telefone\": {\n \"ddd\": \"11\",\n \"numero\": \"999999999\"\n }\n }\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {},
|
||||
"description": "Cadastro de tomador."
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
18
backup/src/Models/config.py
Normal file
18
backup/src/Models/config.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
# Dados de conexão ao banco de dados isolado do cliente e da API PlugNotas
|
||||
class Config(BaseModel):
|
||||
api_key: str | None = None
|
||||
x_api_key: str | None = None
|
||||
user_postgres: str
|
||||
pass_postgres: str
|
||||
host_postgres: str
|
||||
port_postgres: str
|
||||
database_postgres: str
|
||||
|
||||
# Informações de mensageria para a troca de arquivos (AMQP) via subagent
|
||||
class SystemConfig(BaseModel):
|
||||
serviceid: str
|
||||
servicekey: str
|
||||
origin: str
|
||||
amqps: str
|
||||
96
backup/src/Models/empresa.py
Normal file
96
backup/src/Models/empresa.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
###################
|
||||
## POST /empresas #
|
||||
###################
|
||||
class Endereco(BaseModel):
|
||||
codigoPais: str
|
||||
descricaoPais: str
|
||||
tipoLogradouro: str
|
||||
logradouro: str
|
||||
numero: str
|
||||
complemento: str
|
||||
tipoBairro: str
|
||||
bairro: str
|
||||
codigoCidade: str
|
||||
descricaoCidade: str
|
||||
estado: str
|
||||
cep: str
|
||||
|
||||
class Rps(BaseModel):
|
||||
lote: int
|
||||
numeracao: list
|
||||
|
||||
class Email(BaseModel):
|
||||
envio: bool
|
||||
|
||||
class Config(BaseModel):
|
||||
rps: Rps | None = None
|
||||
email: Email | None = None
|
||||
producao: bool | None = None
|
||||
envioLimitado: bool | None = None
|
||||
integracoes: list | None = None
|
||||
prefeitura: dict | str | int | None = None
|
||||
|
||||
class Nfse(BaseModel):
|
||||
config: Config | None = None
|
||||
ativo: bool = False
|
||||
tipoContrato: int | None = 0
|
||||
|
||||
class Dfe(BaseModel):
|
||||
primeiroNsu: bool
|
||||
|
||||
class ConfigNfe(BaseModel):
|
||||
dfe: Dfe | None = None
|
||||
email: Email | None = None
|
||||
integracoes: dict | None = None
|
||||
producao: bool | None = None
|
||||
impressaoFcp: bool | None = None
|
||||
impressaoPartilha: bool | None = None
|
||||
versaoManual: str | None = None
|
||||
versaoEsquema: str | None = None
|
||||
numeracao: list | None = None
|
||||
|
||||
class Nfe(BaseModel):
|
||||
config: ConfigNfe | None = None
|
||||
ativo: bool = False
|
||||
tipoContrato: int | None = 0
|
||||
|
||||
class ConfigNfce(BaseModel):
|
||||
email: Email | None = None
|
||||
integracoes: dict | None = None
|
||||
producao: bool | None = None
|
||||
versaoManual: str | None = None
|
||||
versaoEsquema: str | None = None
|
||||
numeracao: list | None = None
|
||||
|
||||
class Nfce(BaseModel):
|
||||
config: ConfigNfce | None = None
|
||||
ativo: bool = False
|
||||
tipoContrato: int | None = 0
|
||||
|
||||
|
||||
# Entidade Empresa que cadastra a credencial emissora no sistema da PlugNotas
|
||||
class Empresa(BaseModel):
|
||||
endereco: Endereco
|
||||
nfse: Nfse | None = None
|
||||
nfe: Nfe | None = None
|
||||
nfce: Nfce | None = None
|
||||
incentivoFiscal: bool
|
||||
incentivadorCultural: bool
|
||||
cpfCnpj: str
|
||||
inscricaoMunicipal: str
|
||||
inscricaoEstadual: str | None = None
|
||||
certificado: str
|
||||
razaoSocial: str
|
||||
nomeFantasia: str | None = None
|
||||
simplesNacional: bool
|
||||
regimeTributario: int
|
||||
regimeTributarioEspecial: int
|
||||
id: str | None = None
|
||||
banco: str | None = None
|
||||
email: str | None = None
|
||||
id_empresa: int | None = None
|
||||
|
||||
|
||||
99
backup/src/Models/nfe.py
Normal file
99
backup/src/Models/nfe.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
|
||||
# Grupo de dados correspondente ao prestador/emitente da nota
|
||||
class Emitente(BaseModel):
|
||||
cpfCnpj: str
|
||||
inscricaoEstadual: str
|
||||
|
||||
class Endereco(BaseModel):
|
||||
logradouro: str
|
||||
numero: str
|
||||
bairro: str
|
||||
codigoCidade: str
|
||||
descricaoCidade: str
|
||||
estado: str
|
||||
cep: str
|
||||
|
||||
class Destinatario(BaseModel):
|
||||
cpfCnpj: str
|
||||
razaoSocial: str
|
||||
email: Optional[str] = None
|
||||
endereco: Endereco
|
||||
|
||||
class ValorTributavel(BaseModel):
|
||||
comercial: float
|
||||
tributavel: float
|
||||
|
||||
class BaseCalculoIcms(BaseModel):
|
||||
modalidadeDeterminacao: int
|
||||
valor: float
|
||||
|
||||
class Icms(BaseModel):
|
||||
origem: str
|
||||
cst: str
|
||||
baseCalculo: BaseCalculoIcms
|
||||
aliquota: float
|
||||
valor: float
|
||||
|
||||
class BaseCalculoPis(BaseModel):
|
||||
valor: float
|
||||
quantidade: int
|
||||
|
||||
class Pis(BaseModel):
|
||||
cst: str
|
||||
baseCalculo: BaseCalculoPis
|
||||
aliquota: float
|
||||
valor: float
|
||||
|
||||
class BaseCalculoCofins(BaseModel):
|
||||
valor: float
|
||||
|
||||
class Cofins(BaseModel):
|
||||
cst: str
|
||||
baseCalculo: BaseCalculoCofins
|
||||
aliquota: float
|
||||
valor: float
|
||||
|
||||
class Tributos(BaseModel):
|
||||
icms: Optional[Icms] = None
|
||||
pis: Optional[Pis] = None
|
||||
cofins: Optional[Cofins] = None
|
||||
|
||||
# Representa o produto ou serviço vendido, incluindo valores, quantidades e tributação
|
||||
class ItemNFe(BaseModel):
|
||||
codigo: str
|
||||
descricao: str
|
||||
ncm: str
|
||||
cest: Optional[str] = None
|
||||
cfop: str
|
||||
valorUnitario: ValorTributavel
|
||||
valor: float
|
||||
tributos: Tributos
|
||||
|
||||
class Pagamento(BaseModel):
|
||||
aVista: bool
|
||||
meio: str
|
||||
valor: float
|
||||
|
||||
class Telefone(BaseModel):
|
||||
ddd: str
|
||||
numero: str
|
||||
|
||||
class ResponsavelTecnico(BaseModel):
|
||||
cpfCnpj: str
|
||||
nome: str
|
||||
email: str
|
||||
telefone: Telefone
|
||||
|
||||
# Root do JSON para envio da NFe; agrupa recebedor, compras, responsável, e pagamentos
|
||||
class NFe(BaseModel):
|
||||
idIntegracao: str
|
||||
presencial: bool
|
||||
consumidorFinal: bool
|
||||
natureza: str
|
||||
emitente: Emitente
|
||||
destinatario: Destinatario
|
||||
itens: List[ItemNFe]
|
||||
pagamentos: List[Pagamento]
|
||||
responsavelTecnico: Optional[ResponsavelTecnico] = None
|
||||
49
backup/src/Models/nfse.py
Normal file
49
backup/src/Models/nfse.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
class Prestador(BaseModel):
|
||||
cpfCnpj: str
|
||||
|
||||
class Endereco(BaseModel):
|
||||
descricaoCidade: str
|
||||
cep: str
|
||||
tipoLogradouro: str
|
||||
logradouro: str
|
||||
tipoBairro: Optional[str] = None
|
||||
codigoCidade: str
|
||||
complemento: Optional[str] = None
|
||||
estado: str
|
||||
numero: str
|
||||
bairro: str
|
||||
|
||||
class Tomador(BaseModel):
|
||||
cpfCnpj: str
|
||||
razaoSocial: str
|
||||
inscricaoMunicipal: Optional[str] = None
|
||||
email: str
|
||||
endereco: dict
|
||||
|
||||
class ISS(BaseModel):
|
||||
tipoTributacao: int
|
||||
exigibilidade: int
|
||||
aliquota: float
|
||||
|
||||
class Valor(BaseModel):
|
||||
servico: float
|
||||
descontoCondicionado: Optional[float] = 0
|
||||
descontoIncondicionado: Optional[float] = 0
|
||||
|
||||
class Servico(BaseModel):
|
||||
codigo: str
|
||||
codigoTributacao: str
|
||||
discriminacao: str
|
||||
cnae: str
|
||||
iss: ISS
|
||||
valor: Valor
|
||||
|
||||
# Estrutura padrão de envio de uma NFSe contendo Prestador, Tomador e Serviço prestado
|
||||
class NFSe(BaseModel):
|
||||
idIntegracao: str
|
||||
prestador: Prestador
|
||||
tomador: Tomador
|
||||
servico: Servico
|
||||
518
backup/src/Models/payload.py
Normal file
518
backup/src/Models/payload.py
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
from enum import Enum
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from Models.config import Config
|
||||
from Models.empresa import Empresa
|
||||
from Models.nfe import NFe
|
||||
from Models.nfse import NFSe
|
||||
from Models.servico import Servico
|
||||
from Models.tomador import Tomador
|
||||
|
||||
|
||||
class Fila(str, Enum):
|
||||
NFE = "nfe"
|
||||
NFSE = "nfse"
|
||||
CERTIFICADO_DIGITAL = "certificado_digital"
|
||||
EMPRESA = "empresa"
|
||||
SERVICO = "servico"
|
||||
TOMADOR = "tomador"
|
||||
RELATORIOS = "relatorios"
|
||||
WEBHOOKS = "webhooks"
|
||||
|
||||
|
||||
class NFeMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
DOWNLOAD = "download"
|
||||
DOWNLOAD_XML = "download_xml"
|
||||
CANCELAR = "cancelar"
|
||||
STATUS_CANCELAMENTO = "status_cancelamento"
|
||||
CCE = "cce"
|
||||
STATUS_CCE = "status_cce"
|
||||
RESUMO = "resumo"
|
||||
|
||||
|
||||
class NFSeMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
DOWNLOAD = "download"
|
||||
DOWNLOAD_XML = "download_xml"
|
||||
CANCELAR = "cancelar"
|
||||
STATUS_CANCELAMENTO = "status_cancelamento"
|
||||
RESUMO = "resumo"
|
||||
HISTORICO = "historico"
|
||||
|
||||
|
||||
class CertificadoMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
PUT = "put"
|
||||
|
||||
|
||||
class EmpresaMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
POST_FILE = "post_file"
|
||||
PATCH = "patch"
|
||||
|
||||
|
||||
class ServicoMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
PUT = "put"
|
||||
DELETE = "delete"
|
||||
|
||||
|
||||
class TomadorMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
PUT = "put"
|
||||
DELETE = "delete"
|
||||
|
||||
|
||||
class RelatorioMetodo(str, Enum):
|
||||
NFE_EMITIDAS = "nfe_emitidas"
|
||||
NFE_POR_CNPJ = "nfe_por_cnpj"
|
||||
NFSE_EMITIDAS = "nfse_emitidas"
|
||||
NFSE_POR_CNPJ = "nfse_por_cnpj"
|
||||
|
||||
|
||||
class WebhookMetodo(str, Enum):
|
||||
GET = "get"
|
||||
POST = "post"
|
||||
DELETE = "delete"
|
||||
PUT = "put"
|
||||
TEST = "test"
|
||||
|
||||
|
||||
class BaseEnvelope(BaseModel):
|
||||
cliente: str | None = None
|
||||
config: Config
|
||||
url_base: str | None = None
|
||||
|
||||
|
||||
# --- Base Data Models ---
|
||||
|
||||
class BaseDados(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class NFePostDadosWrapped(BaseDados):
|
||||
nfe: NFe
|
||||
id_empresa: str | None = None
|
||||
|
||||
|
||||
class NFePostDadosDirect(NFe):
|
||||
id_usuario: int | None = 1
|
||||
id_empresa: str | None = None
|
||||
|
||||
|
||||
class NFeIdDados(BaseDados):
|
||||
id: str
|
||||
|
||||
|
||||
class NFeCancelarDados(NFeIdDados):
|
||||
justificativa: str | None = None
|
||||
|
||||
|
||||
class NFeCCEDados(NFeIdDados):
|
||||
correcao: str
|
||||
|
||||
|
||||
class NFeResumoDados(BaseDados):
|
||||
cnpj: str
|
||||
id_integracao: str | None = None
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class NFSePostDados(NFSe):
|
||||
id_usuario: int | None = 1
|
||||
id_empresa: str | None = None
|
||||
|
||||
|
||||
class NFSeIdDados(BaseDados):
|
||||
id: str
|
||||
|
||||
|
||||
class NFSeGetDados(BaseDados):
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class NFSeResumoDados(NFSeIdDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class NFSeHistoricoDados(NFSeIdDados):
|
||||
cnpj: str | None = None
|
||||
|
||||
|
||||
class CertificadoPostDados(BaseDados):
|
||||
arquivo: str
|
||||
senha: str
|
||||
id_empresa: str | None = None
|
||||
email: str | None = None
|
||||
expiration_at: str | None = Field(None, alias="expirationAt")
|
||||
data_expiracao: str | None = None
|
||||
|
||||
|
||||
class CertificadoGetDados(BaseDados):
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class CertificadoPutDados(CertificadoPostDados):
|
||||
id: str
|
||||
|
||||
|
||||
class EmpresaPostDados(Empresa):
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class EmpresaGetDados(BaseDados):
|
||||
cnpj: str | None = None
|
||||
|
||||
|
||||
class EmpresaPostFileDados(BaseDados):
|
||||
file: str
|
||||
cnpj: str
|
||||
|
||||
|
||||
class EmpresaPatchDados(BaseDados):
|
||||
cnpj: str
|
||||
certificado: str
|
||||
|
||||
|
||||
class ServicoPostDados(Servico):
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class ServicoGetDados(BaseDados):
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class ServicoPutDados(Servico):
|
||||
id: str
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class ServicoDeleteDados(BaseDados):
|
||||
id: str
|
||||
|
||||
|
||||
class TomadorPostDados(Tomador):
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class TomadorGetDados(BaseDados):
|
||||
cpf_cnpj: str | None = None
|
||||
|
||||
|
||||
class TomadorPutDados(Tomador):
|
||||
cpf_cnpj: str
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class TomadorDeleteDados(BaseDados):
|
||||
cpf_cnpj: str
|
||||
|
||||
|
||||
class RelatorioVazioDados(BaseDados):
|
||||
pass
|
||||
|
||||
|
||||
class RelatorioCnpjDados(BaseDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class RelatorioPeriodoDados(BaseDados):
|
||||
data_inicial: str | None = None
|
||||
data_final: str | None = None
|
||||
|
||||
|
||||
class RelatorioCnpjPeriodoDados(RelatorioPeriodoDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class WebhookGetDeleteTestDados(BaseDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class WebhookPostPutDados(WebhookGetDeleteTestDados):
|
||||
url: str
|
||||
|
||||
|
||||
# --- Envelopes ---
|
||||
|
||||
class NFePostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.POST
|
||||
dados: NFePostDadosDirect | NFePostDadosWrapped
|
||||
|
||||
|
||||
class NFeIdEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo
|
||||
dados: NFeIdDados
|
||||
|
||||
|
||||
class NFeCancelarEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.CANCELAR
|
||||
dados: NFeCancelarDados
|
||||
|
||||
|
||||
class NFeCCEEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.CCE
|
||||
dados: NFeCCEDados
|
||||
|
||||
|
||||
class NFeResumoEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.RESUMO
|
||||
dados: NFeResumoDados
|
||||
|
||||
|
||||
class NFSePostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.POST
|
||||
dados: NFSePostDados
|
||||
|
||||
|
||||
class NFSeGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.GET
|
||||
dados: NFSeGetDados
|
||||
|
||||
|
||||
class NFSeIdEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo
|
||||
dados: NFSeIdDados
|
||||
|
||||
|
||||
class NFSeResumoEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.RESUMO
|
||||
dados: NFSeResumoDados
|
||||
|
||||
|
||||
class NFSeHistoricoEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.HISTORICO
|
||||
dados: NFSeHistoricoDados
|
||||
|
||||
|
||||
class CertificadoPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.CERTIFICADO_DIGITAL
|
||||
metodo: CertificadoMetodo = CertificadoMetodo.POST
|
||||
dados: CertificadoPostDados
|
||||
|
||||
|
||||
class CertificadoGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.CERTIFICADO_DIGITAL
|
||||
metodo: CertificadoMetodo = CertificadoMetodo.GET
|
||||
dados: CertificadoGetDados
|
||||
|
||||
|
||||
class CertificadoPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.CERTIFICADO_DIGITAL
|
||||
metodo: CertificadoMetodo = CertificadoMetodo.PUT
|
||||
dados: CertificadoPutDados
|
||||
|
||||
|
||||
class EmpresaPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.POST
|
||||
dados: EmpresaPostDados
|
||||
|
||||
|
||||
class EmpresaGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.GET
|
||||
dados: EmpresaGetDados
|
||||
|
||||
|
||||
class EmpresaPostFileEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.POST_FILE
|
||||
dados: EmpresaPostFileDados
|
||||
|
||||
|
||||
class EmpresaPatchEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.PATCH
|
||||
dados: EmpresaPatchDados
|
||||
|
||||
|
||||
class ServicoPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.POST
|
||||
dados: ServicoPostDados
|
||||
|
||||
|
||||
class ServicoGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.GET
|
||||
dados: ServicoGetDados
|
||||
|
||||
|
||||
class ServicoPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.PUT
|
||||
dados: ServicoPutDados
|
||||
|
||||
|
||||
class ServicoDeleteEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.DELETE
|
||||
dados: ServicoDeleteDados
|
||||
|
||||
|
||||
class TomadorPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.POST
|
||||
dados: TomadorPostDados
|
||||
|
||||
|
||||
class TomadorGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.GET
|
||||
dados: TomadorGetDados
|
||||
|
||||
|
||||
class TomadorPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.PUT
|
||||
dados: TomadorPutDados
|
||||
|
||||
|
||||
class TomadorDeleteEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.DELETE
|
||||
dados: TomadorDeleteDados
|
||||
|
||||
|
||||
class RelatorioNFeEmitidasEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFE_EMITIDAS
|
||||
dados: RelatorioVazioDados
|
||||
|
||||
|
||||
class RelatorioNFeCnpjEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFE_POR_CNPJ
|
||||
dados: RelatorioCnpjDados
|
||||
|
||||
|
||||
class RelatorioNFSeEmitidasEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFSE_EMITIDAS
|
||||
dados: RelatorioPeriodoDados
|
||||
|
||||
|
||||
class RelatorioNFSeCnpjEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFSE_POR_CNPJ
|
||||
dados: RelatorioCnpjPeriodoDados
|
||||
|
||||
|
||||
class WebhookGetDeleteTestEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.WEBHOOKS
|
||||
metodo: WebhookMetodo
|
||||
dados: WebhookGetDeleteTestDados
|
||||
|
||||
|
||||
class WebhookPostPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.WEBHOOKS
|
||||
metodo: WebhookMetodo
|
||||
dados: WebhookPostPutDados
|
||||
|
||||
|
||||
# --- Generic Envelope for basic structural validation ---
|
||||
|
||||
class GenericEnvelope(BaseEnvelope):
|
||||
"""Validates that minimum required fields exist before routing."""
|
||||
fila: Fila
|
||||
metodo: str
|
||||
dados: dict | None = None
|
||||
|
||||
|
||||
# --- Payload Validation Router ---
|
||||
|
||||
# Maps (fila, metodo) -> Envelope class
|
||||
_ENVELOPE_MAP: dict[tuple[str, str], type[BaseEnvelope]] = {
|
||||
# NFe
|
||||
("nfe", "post"): NFePostEnvelope,
|
||||
("nfe", "get"): NFeIdEnvelope,
|
||||
("nfe", "download"): NFeIdEnvelope,
|
||||
("nfe", "download_xml"): NFeIdEnvelope,
|
||||
("nfe", "cancelar"): NFeCancelarEnvelope,
|
||||
("nfe", "status_cancelamento"): NFeIdEnvelope,
|
||||
("nfe", "cce"): NFeCCEEnvelope,
|
||||
("nfe", "status_cce"): NFeIdEnvelope,
|
||||
("nfe", "resumo"): NFeResumoEnvelope,
|
||||
# NFSe
|
||||
("nfse", "post"): NFSePostEnvelope,
|
||||
("nfse", "get"): NFSeGetEnvelope,
|
||||
("nfse", "download"): NFSeIdEnvelope,
|
||||
("nfse", "download_xml"): NFSeIdEnvelope,
|
||||
("nfse", "cancelar"): NFSeIdEnvelope,
|
||||
("nfse", "status_cancelamento"): NFSeIdEnvelope,
|
||||
("nfse", "resumo"): NFSeResumoEnvelope,
|
||||
("nfse", "historico"): NFSeHistoricoEnvelope,
|
||||
# Certificado Digital
|
||||
("certificado_digital", "post"): CertificadoPostEnvelope,
|
||||
("certificado_digital", "get"): CertificadoGetEnvelope,
|
||||
("certificado_digital", "put"): CertificadoPutEnvelope,
|
||||
# Empresa
|
||||
("empresa", "post"): EmpresaPostEnvelope,
|
||||
("empresa", "get"): EmpresaGetEnvelope,
|
||||
("empresa", "post_file"): EmpresaPostFileEnvelope,
|
||||
("empresa", "patch"): EmpresaPatchEnvelope,
|
||||
# Serviço
|
||||
("servico", "post"): ServicoPostEnvelope,
|
||||
("servico", "get"): ServicoGetEnvelope,
|
||||
("servico", "put"): ServicoPutEnvelope,
|
||||
("servico", "delete"): ServicoDeleteEnvelope,
|
||||
# Tomador
|
||||
("tomador", "post"): TomadorPostEnvelope,
|
||||
("tomador", "get"): TomadorGetEnvelope,
|
||||
("tomador", "put"): TomadorPutEnvelope,
|
||||
("tomador", "delete"): TomadorDeleteEnvelope,
|
||||
# Relatórios
|
||||
("relatorios", "nfe_emitidas"): RelatorioNFeEmitidasEnvelope,
|
||||
("relatorios", "nfe_por_cnpj"): RelatorioNFeCnpjEnvelope,
|
||||
("relatorios", "nfse_emitidas"): RelatorioNFSeEmitidasEnvelope,
|
||||
("relatorios", "nfse_por_cnpj"): RelatorioNFSeCnpjEnvelope,
|
||||
# Webhooks
|
||||
("webhooks", "get"): WebhookGetDeleteTestEnvelope,
|
||||
("webhooks", "post"): WebhookPostPutEnvelope,
|
||||
("webhooks", "delete"): WebhookGetDeleteTestEnvelope,
|
||||
("webhooks", "put"): WebhookPostPutEnvelope,
|
||||
("webhooks", "test"): WebhookGetDeleteTestEnvelope,
|
||||
}
|
||||
|
||||
|
||||
def validate_payload(payload_dict: dict) -> BaseEnvelope:
|
||||
# Step 1: Basic structural validation
|
||||
generic = GenericEnvelope(**payload_dict)
|
||||
|
||||
fila = generic.fila.value
|
||||
metodo = generic.metodo.lower()
|
||||
|
||||
# Step 2: Find specific envelope
|
||||
key = (fila, metodo)
|
||||
envelope_cls = _ENVELOPE_MAP.get(key)
|
||||
|
||||
if envelope_cls is None:
|
||||
raise ValueError(f"Combinação fila/metodo desconhecida: {fila}/{metodo}")
|
||||
|
||||
# Step 3: Normalize metodo in the dict to match enum lowercase values
|
||||
payload_dict['metodo'] = metodo
|
||||
|
||||
# Step 4: Full typed validation
|
||||
return envelope_cls(**payload_dict)
|
||||
|
||||
|
||||
|
||||
150
backup/src/Models/servico.py
Normal file
150
backup/src/Models/servico.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
from pydantic import BaseModel
|
||||
from enum import IntEnum
|
||||
|
||||
class TipoTributacao(IntEnum):
|
||||
NAO_DEFINIDO = 0
|
||||
ISENTO_ISS = 1
|
||||
IMUNE = 2
|
||||
NAO_INCIDENCIA = 3
|
||||
NAO_TRIBUTAVEL = 4
|
||||
RETIDO = 5
|
||||
TRIBUTAVEL_MUNICIPIO = 6
|
||||
TRIBUTAVEL_FORA_MUNICIPIO = 7
|
||||
TRIBUTAVEL_MUNICIPIO_TOMADOR = 8
|
||||
|
||||
class TipoExigibilidade(IntEnum):
|
||||
EXIGIVEL = 1
|
||||
NAO_INCIDENCIA = 2
|
||||
ISENCAO = 3
|
||||
EXPORTACAO = 4
|
||||
IMUNIDADE = 5
|
||||
SUSPENSO_ACAO_JUDICIAL = 6
|
||||
SUSPENSO_ACAO_ADMINISTRATIVA = 7
|
||||
|
||||
class ISS(BaseModel):
|
||||
tipoTributacao: int #TipoTributacao
|
||||
exigibilidade: int #TipoExigibilidade
|
||||
retido: bool | None = None # Se o ISS foi retido na fonte
|
||||
aliquota: float # Aliquota do ISS
|
||||
valor: float # Valor do ISS
|
||||
valorRetido: float # Valor do ISS retido
|
||||
processoSuspensao: str # Numero processo de suspensão do ISS
|
||||
##############################################################################
|
||||
# tipoTributacao integer
|
||||
# Default: 6
|
||||
# Enum: 0 1 2 3 4 5 6 7 8
|
||||
# Tipo de Tributação do Serviço, valores:
|
||||
# 0 - Não definido
|
||||
# 1 - Isento de ISS
|
||||
# 2 - Imune
|
||||
# 3 - Não Incidência no Município
|
||||
# 4 - Não Tributável
|
||||
# 5 - Retido
|
||||
# 6 - Tributável Dentro do Município
|
||||
# 7 - Tributável Fora do Município
|
||||
# 8 - Tributável Dentro do Município pelo tomador
|
||||
##############################################################################
|
||||
# exigibilidade integer
|
||||
# Default: 1
|
||||
# Enum: 1 2 3 4 5 6 7
|
||||
# Exigibilidade do ISS, valores:
|
||||
# 1 - Exigível
|
||||
# 2 - Não Incidência
|
||||
# 3 - Isenção
|
||||
# 4 - Exportação
|
||||
# 5 - Imunidade
|
||||
# 6 - Supenso por Ação Judicial
|
||||
# 7 - Suspenso por Ação Administrativa
|
||||
##############################################################################
|
||||
# responsavelRetencao string
|
||||
# Valores aceitos:
|
||||
# 1 - Tomador
|
||||
# 2 - Intermediário
|
||||
##############################################################################
|
||||
|
||||
class Obra(BaseModel):
|
||||
art: str # Anotação de Responsabilidade Técnica (Codigo ART)
|
||||
codigo: str # Código da obra
|
||||
|
||||
class Valor(BaseModel):
|
||||
servico: float # Valor bruto do serviço prestado.
|
||||
baseCalculo: float # Valor da base de cálculo dos impostos.
|
||||
deducoes: float # Valor deduções.
|
||||
descontoCondicionado: float # Valor do desconto condicionado.
|
||||
descontoIncondicionado: float # Valor do desconto incondicionado.
|
||||
liquido: float # Valor liquido do serviço, caso você não informe esse campo, vamos calcular automáticamente.
|
||||
unitario: float # Valor unitário do serviço.
|
||||
valorAproximadoTributos: float # Valor aproximado dos tributos.
|
||||
|
||||
class Deducao(BaseModel):
|
||||
tipo: int
|
||||
descricao: str # Descrição da dedução
|
||||
##############################################################################
|
||||
# tipo integer
|
||||
# Tipo de dedução:
|
||||
# 0 - Sem Deduções
|
||||
# 1 - Materiais
|
||||
# 2 - Subempreitada de Mão de Obra
|
||||
# 3 - Serviços
|
||||
# 4 - Produção Externa
|
||||
# 5 - Alimentação e Bebidas
|
||||
# 6 - Reembolso de Despesas
|
||||
# 7 - Repasse Consorciado
|
||||
# 8 - Repasse Plano de Saúde
|
||||
# 99 - Outras Deduções
|
||||
##############################################################################
|
||||
|
||||
class RetencaoInterno(BaseModel):
|
||||
aliquota: float # Aliquota da retenção
|
||||
valor: float # Valor da retenção
|
||||
|
||||
class Retencao(BaseModel):
|
||||
pis: RetencaoInterno
|
||||
cofins: RetencaoInterno
|
||||
csll: RetencaoInterno
|
||||
inss: RetencaoInterno
|
||||
irrf: RetencaoInterno
|
||||
outrasRetencoes: float # Valor de outras retenções
|
||||
cpp: RetencaoInterno
|
||||
|
||||
class Aliquota(BaseModel):
|
||||
municipal: float # Aliquota municipal
|
||||
estadual: float # Aliquota estadual
|
||||
federal: float # Aliquota federal
|
||||
|
||||
class Detalhado(BaseModel):
|
||||
aliquota: Aliquota
|
||||
|
||||
class Simplificado(BaseModel):
|
||||
aliquota: float # Aliquota geral de impostos
|
||||
|
||||
class IBPT(BaseModel):
|
||||
simplificado: Simplificado
|
||||
detalhado: Detalhado
|
||||
|
||||
# Representa o detalhamento do serviço prestado na NFSe, impostos incidentes e deduções
|
||||
class Servico(BaseModel):
|
||||
codigo: str # Código de serviço, também conhecido como Código Item Lista Serviço, geralmente segue a LC116, podendo váriar de acordo com a prefeitura.Você pode encontrar esse dado no portal da prefeitura, em uma nota emitida, ou junto ao contador.
|
||||
idIntegracao: str # Código EXTERNO de integração do serviço. Utilizado SOMENTE quando se cadastra previamente um serviço.
|
||||
discriminacao: str # Descrição do serviço prestado
|
||||
codigoTributacao: str # Código tributação no Município
|
||||
cnae: str # O código CNAE ou, por extenso, Classificação Nacional de Atividades Econômicas, é uma forma de padronizar, em todo o território nacional, os códigos de atividades econômicas e os critérios de enquadramento usados pelos mais diversos órgãos da administração tributária do Brasil.
|
||||
codigoCidadeIncidencia: str # Código IBGE da cidade de incidência do ISS.
|
||||
descricaoCidadeIncidencia: str # Descrição da cidade de incidência do ISS.
|
||||
unidade: str # Unidade do serviço prestado.
|
||||
quantidade: int # Quantidade dos serviços prestados.
|
||||
iss: ISS
|
||||
obra: Obra
|
||||
valor: Valor
|
||||
deducao: Deducao
|
||||
retencao: Retencao
|
||||
tributavel: bool # Indica se o serviço está sujeito a tributação.
|
||||
ibpt: IBPT
|
||||
responsavelRetencao: str
|
||||
|
||||
##############################################################################
|
||||
# responsavelRetencao Valores aceitos:
|
||||
# 1 - Tomador
|
||||
# 2 - Intermediário
|
||||
# Caso o responsável pela retenção seja o próprio prestador, não gerar o campo.
|
||||
##############################################################################
|
||||
34
backup/src/Models/tomador.py
Normal file
34
backup/src/Models/tomador.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Endereco(BaseModel):
|
||||
tipoLogradouro: str
|
||||
logradouro: str
|
||||
numero: str
|
||||
complemento: str
|
||||
tipoBairro: str
|
||||
bairro: str
|
||||
codigoPais: str
|
||||
descricaoPais: str
|
||||
codigoCidade: str
|
||||
descricaoCidade: str
|
||||
estado: str
|
||||
cep: str
|
||||
|
||||
class Telefone(BaseModel):
|
||||
ddd: str
|
||||
numero: str
|
||||
|
||||
# Representa o cliente (Tomador do Serviço) vinculado a uma nota fiscal de serviço
|
||||
class Tomador(BaseModel):
|
||||
cpfCnpj: str
|
||||
inscricaoMunicipal: str
|
||||
inscricaoEstadual: str
|
||||
inscricaoSuframa: str
|
||||
indicadorInscricaoEstadual: int
|
||||
razaoSocial: str
|
||||
nomeFantasia: str
|
||||
endereco: Endereco
|
||||
telefone: Telefone
|
||||
email: str
|
||||
orgaoPublico: bool
|
||||
105
backup/src/NFSe/Servico/servico.py
Normal file
105
backup/src/NFSe/Servico/servico.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
from Models.servico import Servico
|
||||
from utils.api import Api
|
||||
from utils.banco import Banco, ServicoTable
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
import json
|
||||
|
||||
# Controller responsável pelos cadastros e busca de Serviços a serem vinculados em NFSe
|
||||
class ServicoController:
|
||||
def __init__(self, url_base, api_key, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/nfse/servico"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def cadastrar_servico(self, servico: Servico, data: dict):
|
||||
try:
|
||||
resposta = self.api.post(self.url, servico.model_dump())
|
||||
if 'error' in resposta:
|
||||
self._log(RealmType.ERRORS, f"Erro na API ao cadastrar serviço")
|
||||
return resposta
|
||||
|
||||
servico_novo = ServicoTable(
|
||||
descricao=servico.discriminacao,
|
||||
id_tecnospeed=resposta['data']['id'],
|
||||
id_usuario=data.get('id_usuario', 1),
|
||||
status=True
|
||||
)
|
||||
self.banco.add(servico_novo)
|
||||
self._log(RealmType.OPERATIONAL, f"Serviço cadastrado com sucesso - ID: {resposta['data']['id']}")
|
||||
return resposta
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro no banco ao cadastrar serviço: {e}")
|
||||
return {"error": f"Erro ao cadastrar serviço: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def buscar_servico(self, id_servico, data: dict = None):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_servico}")
|
||||
self._log(RealmType.OPERATIONAL, f"Serviço consultado - ID: {id_servico}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao buscar serviço: {e}")
|
||||
return {"error": f"Erro ao buscar serviço: {str(e)}"}
|
||||
|
||||
def listar_servicos(self, data: dict = None):
|
||||
try:
|
||||
servicos = self.banco.session.query(ServicoTable.id_tecnospeed, ServicoTable.descricao, ServicoTable.status).all()
|
||||
resultado = [{"id_tecnospeed": servico.id_tecnospeed, "descricao": servico.descricao, "status": servico.status} for servico in servicos]
|
||||
self._log(RealmType.OPERATIONAL, f"Listagem de serviços - {len(resultado)} registros")
|
||||
return resultado
|
||||
except SQLAlchemyError as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao listar serviços: {e}")
|
||||
return {"error": f"Erro ao listar serviços: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def atualizar_servico(self, id_servico, servico: Servico):
|
||||
try:
|
||||
resposta = self.api.patch(f"{self.url}/{id_servico}", servico.model_dump())
|
||||
servico_existente = self.banco.session.query(ServicoTable).filter_by(id=id_servico).first()
|
||||
|
||||
if not servico_existente:
|
||||
self._log(RealmType.ERRORS, f"Serviço não encontrado - ID: {id_servico}")
|
||||
return {"error": "Serviço não encontrado"}
|
||||
|
||||
servico_existente.descricao = servico.discriminacao
|
||||
self.banco.session.commit()
|
||||
|
||||
self._log(RealmType.OPERATIONAL, f"Serviço atualizado com sucesso - ID: {id_servico}")
|
||||
return resposta
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro ao atualizar serviço: {e}")
|
||||
return {"error": f"Erro ao atualizar serviço: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def deletar_servico(self, id_servico):
|
||||
try:
|
||||
resposta = self.api.delete(f"{self.url}/{id_servico}")
|
||||
servico_existente = self.banco.session.query(ServicoTable).filter_by(id=id_servico).first()
|
||||
|
||||
if not servico_existente:
|
||||
self._log(RealmType.ERRORS, f"Serviço não encontrado para deletar - ID: {id_servico}")
|
||||
return {"error": "Serviço não encontrado"}
|
||||
|
||||
self.banco.session.delete(servico_existente)
|
||||
self.banco.session.commit()
|
||||
|
||||
self._log(RealmType.OPERATIONAL, f"Serviço deletado com sucesso - ID: {id_servico}")
|
||||
return resposta
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro ao deletar serviço: {e}")
|
||||
return {"error": f"Erro ao deletar serviço: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
91
backup/src/NFSe/Tomador/tomador.py
Normal file
91
backup/src/NFSe/Tomador/tomador.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
from utils.api import Api
|
||||
from utils.banco import Banco, TomadorTable
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from Models.tomador import Tomador
|
||||
|
||||
import json
|
||||
|
||||
# Controller para gerenciamento no banco e na API dos Tomadores (clientes finais) das notas
|
||||
class TomadorController:
|
||||
def __init__(self, url_base, api_key, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/nfse/tomador"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def cadastrar_tomador(self, tomador: Tomador, data: dict):
|
||||
try:
|
||||
retorno = self.api.post(self.url, tomador.model_dump())
|
||||
if 'error' in retorno:
|
||||
self._log(RealmType.ERRORS, f"Erro na API ao cadastrar tomador")
|
||||
return retorno
|
||||
|
||||
tomador_novo = TomadorTable(
|
||||
id_tecnospeed=retorno['id'],
|
||||
nome=tomador.razaoSocial,
|
||||
cpf_cnpj=tomador.cpfCnpj,
|
||||
email=tomador.email,
|
||||
telefone=f"({tomador.telefone.ddd}) {tomador.telefone.numero}",
|
||||
id_usuario=data.get('id_usuario', 1),
|
||||
status=True
|
||||
)
|
||||
|
||||
self.banco.add(tomador_novo)
|
||||
self._log(RealmType.OPERATIONAL, f"Tomador cadastrado com sucesso - CPF/CNPJ: {tomador.cpfCnpj}")
|
||||
return retorno
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao cadastrar tomador: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def buscar_tomador(self, cpf_cnpj):
|
||||
try:
|
||||
tomador = self.api.get(f"{self.url}/{cpf_cnpj}")
|
||||
self._log(RealmType.OPERATIONAL, f"Tomador consultado - CPF/CNPJ: {cpf_cnpj}")
|
||||
return tomador
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao buscar tomador: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def atualizar_tomador(self, cpf_cnpj, tomador: Tomador):
|
||||
try:
|
||||
tomador_existente = self.banco.session.query(TomadorTable).filter_by(cpf_cnpj=cpf_cnpj).first()
|
||||
if not tomador_existente:
|
||||
self._log(RealmType.ERRORS, f"Tomador não encontrado - CPF/CNPJ: {cpf_cnpj}")
|
||||
return {"error": "Tomador não encontrado"}
|
||||
|
||||
resposta = self.api.patch(f"{self.url}/{cpf_cnpj}", tomador.model_dump())
|
||||
tomador_existente.nome = tomador.razaoSocial
|
||||
tomador_existente.email = tomador.email
|
||||
tomador_existente.telefone = f"({tomador.telefone.ddd}) {tomador.telefone.numero}"
|
||||
|
||||
self.banco.session.commit()
|
||||
self._log(RealmType.OPERATIONAL, f"Tomador atualizado com sucesso - CPF/CNPJ: {cpf_cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self.banco.session.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro ao atualizar tomador: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def deletar_tomador(self, cpf_cnpj):
|
||||
try:
|
||||
tomador_existente = self.banco.session.query(TomadorTable).filter_by(cpf_cnpj=cpf_cnpj).first()
|
||||
if not tomador_existente:
|
||||
self._log(RealmType.ERRORS, f"Tomador não encontrado para deletar - CPF/CNPJ: {cpf_cnpj}")
|
||||
return {"error": "Tomador não encontrado"}
|
||||
|
||||
resposta = self.api.delete(f"{self.url}/{cpf_cnpj}")
|
||||
self.banco.session.delete(tomador_existente)
|
||||
self.banco.session.commit()
|
||||
|
||||
self._log(RealmType.OPERATIONAL, f"Tomador deletado com sucesso - CPF/CNPJ: {cpf_cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self.banco.session.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro ao deletar tomador: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
167
backup/src/NFSe/nfse.py
Normal file
167
backup/src/NFSe/nfse.py
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
from Models.nfse import NFSe
|
||||
from utils.api import Api
|
||||
from utils.banco import Banco, NFSeTable
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from utils.notificador import NotificadorInterno
|
||||
|
||||
import json
|
||||
|
||||
# Serviço responsável pelo controle, emissão e cancelamento das Notas Fiscais de Serviço Eletrônicas (NFSe)
|
||||
class NFSeService:
|
||||
def __init__(self, api_key, url_base, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/nfse"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
self.notificador = NotificadorInterno(logger=logger) if logger else None
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def cadastrar_nfse(self, nfse: NFSe, data: dict):
|
||||
try:
|
||||
id_empresa = data.get('id_empresa', "0")
|
||||
resposta = self.api.post(self.url, [nfse.model_dump()])
|
||||
if 'error' in resposta:
|
||||
self._log(RealmType.ERRORS, f"Erro na API ao cadastrar NFSe: {resposta.get('error')}")
|
||||
return resposta
|
||||
|
||||
# A API da Plugnotas retorna 'documents' contendo uma lista com os sucessos
|
||||
documentos = resposta.get('documents', [])
|
||||
if not documentos:
|
||||
self._log(RealmType.ERRORS, f"Nenhum documento retornado pela API. Resposta: {resposta}")
|
||||
return resposta
|
||||
|
||||
id_recebido = documentos[0].get('id')
|
||||
if not id_recebido:
|
||||
raise Exception("ID do documento não encontrado na resposta.")
|
||||
|
||||
nfse_nova = NFSeTable(
|
||||
id_tecnospeed=id_recebido,
|
||||
id_empresa=id_empresa,
|
||||
id_usuario=data.get('id_usuario', 1),
|
||||
status=True
|
||||
)
|
||||
|
||||
self.banco.add(nfse_nova)
|
||||
self._log(RealmType.OPERATIONAL, f"NFSe cadastrada com sucesso - ID: {id_recebido}")
|
||||
|
||||
# Notifica o listener interno para sincronização imediata
|
||||
if self.notificador:
|
||||
self.notificador.notificar_emissao(id_tecnospeed=id_recebido, documento="nfse", cnpj=id_empresa)
|
||||
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao cadastrar NFSe: {e}")
|
||||
raise Exception(f"Erro ao cadastrar NFSe: {e}")
|
||||
|
||||
def buscar_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nfse}")
|
||||
self._log(RealmType.OPERATIONAL, f"NFSe consultada com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao buscar NFSe: {e}")
|
||||
raise Exception(f"Erro ao buscar NFSe: {e}")
|
||||
|
||||
def listar_nfse(self):
|
||||
try:
|
||||
nfse = self.banco.session.query(NFSeTable.id, NFSeTable.id_tecnospeed).all()
|
||||
resultado = [{"id": n[0], "descricao": n[1]} for n in nfse]
|
||||
self._log(RealmType.OPERATIONAL, f"Listagem de NFSe - {len(resultado)} registros")
|
||||
return resultado
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao listar NFSe: {e}")
|
||||
raise Exception(f"Erro ao listar NFSe: {e}")
|
||||
|
||||
def baixar_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/pdf/{id_nfse}")
|
||||
self._log(RealmType.OPERATIONAL, f"PDF NFSe baixado com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao baixar NFSe: {e}")
|
||||
raise Exception(f"Erro ao baixar NFSe: {e}")
|
||||
|
||||
def baixar_xml_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/xml/{id_nfse}")
|
||||
self._log(RealmType.OPERATIONAL, f"XML NFSe baixado com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao baixar XML NFSe: {e}")
|
||||
raise Exception(f"Erro ao baixar XML NFSe: {e}")
|
||||
|
||||
def solicitar_cancelamento_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/cancelar/{id_nfse}", {})
|
||||
self._log(RealmType.OPERATIONAL, f"Cancelamento NFSe solicitado - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao solicitar cancelamento NFSe: {e}")
|
||||
raise Exception(f"Erro ao solicitar cancelamento NFSe: {e}")
|
||||
|
||||
def consultar_status_cancelamento_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/cancelar/status/{id_nfse}")
|
||||
self._log(RealmType.OPERATIONAL, f"Status cancelamento NFSe consultado - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao consultar status cancelamento NFSe: {e}")
|
||||
raise Exception(f"Erro ao consultar status cancelamento NFSe: {e}")
|
||||
|
||||
def consultar_resumo_nfse(self, id_nfse, cnpj):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/consultar/{id_nfse}/{cnpj}")
|
||||
self._log(RealmType.OPERATIONAL, f"Resumo NFSe consultado - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao consultar resumo NFSe: {e}")
|
||||
raise Exception(f"Erro ao consultar resumo NFSe: {e}")
|
||||
|
||||
def baixar_histotico_nfse(self, id_nfse, cnpj = None):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/historico/{id_nfse}") if cnpj is None else self.api.get(f"{self.url}/historico/{id_nfse}/{cnpj}")
|
||||
self._log(RealmType.OPERATIONAL, f"Histórico NFSe baixado - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao baixar histórico NFSe: {e}")
|
||||
raise Exception(f"Erro ao baixar histórico NFSe: {e}")
|
||||
|
||||
def enviar_email_nfse(self, id_nfse, email:list[str], reenvio:bool = False):
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/email/{id_nfse}", {"reenvio": reenvio,"destinatarios": email})
|
||||
self._log(RealmType.OPERATIONAL, f"Email NFSe enviado para {email} - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao enviar email NFSe: {e}")
|
||||
raise Exception(f"Erro ao enviar email NFSe: {e}")
|
||||
|
||||
def resolver_nfse(self, id_nfse, identificacaoNota:str):
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/resolve/{id_nfse}", {"identificacaoNota": identificacaoNota})
|
||||
self._log(RealmType.OPERATIONAL, f"NFSe resolvida com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao resolver NFSe: {e}")
|
||||
raise Exception(f"Erro ao resolver NFSe: {e}")
|
||||
|
||||
def consultar_nfse_periodo(self, data_inicio, data_fim):
|
||||
try:
|
||||
resposta = self.api.get_paginated(f"{self.url}/periodo/{data_inicio}/{data_fim}")
|
||||
self._log(RealmType.OPERATIONAL, f"NFSe consultada por período: {data_inicio} a {data_fim}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao consultar NFSe por período: {e}")
|
||||
raise Exception(f"Erro ao consultar NFSe por período: {e}")
|
||||
|
||||
def baixar_recibo_provisorio_servico(self, id_nfse, cnpj = None):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/rps/pdf/{id_nfse}") if cnpj is None else self.api.get(f"{self.url}/rps/pdf/{id_nfse}/{cnpj}")
|
||||
self._log(RealmType.OPERATIONAL, f"RPS baixado com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao baixar recibo provisório de serviço: {e}")
|
||||
raise Exception(f"Erro ao baixar recibo provisório de serviço: {e}")
|
||||
126
backup/src/NFe/registroNFe.py
Normal file
126
backup/src/NFe/registroNFe.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import json as json_lib
|
||||
from utils.api import Api
|
||||
from utils.banco import Banco, NFeTable
|
||||
from Models.nfe import NFe
|
||||
from typing import List
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from utils.notificador import NotificadorInterno
|
||||
|
||||
class RegistroNFeService:
|
||||
# Classe responsável por fornecer as operações de comunicação com a PlugNotas para o documento NFe
|
||||
def __init__(self, api_key: str, url_base: str, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/nfe"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
self.notificador = NotificadorInterno(logger=logger) if logger else None
|
||||
|
||||
def cadastrar_nfe(self, nfe_list: List[NFe], data: dict):
|
||||
# Envia uma lista de NFes em lote ou unitária para emissão e registra no banco de dados
|
||||
try:
|
||||
id_empresa = data.get('id_empresa', "0")
|
||||
payload = [nfe.model_dump() for nfe in nfe_list]
|
||||
resposta = self.api.post(self.url, payload)
|
||||
if 'error' in resposta:
|
||||
id_usr = data.get('id_usuario', 1)
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'nfe', id_usr, '0', 'ERRO', f"Erro na API: {resposta.get('error')}", json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
if 'documents' in resposta:
|
||||
for doc in resposta['documents']:
|
||||
id_tecnospeed = doc.get('id')
|
||||
if id_tecnospeed:
|
||||
nfe_nova = NFeTable(
|
||||
id_tecnospeed=id_tecnospeed,
|
||||
id_empresa=id_empresa,
|
||||
id_usuario=data.get('id_usuario', 1),
|
||||
status=True
|
||||
)
|
||||
self.banco.add(nfe_nova)
|
||||
id_usr = data.get('id_usuario', 1)
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'nfe', id_usr, str(nfe_nova.id), 'SUCESSO', 'NFe cadastrada com sucesso', json_lib.dumps(doc)))
|
||||
|
||||
# Notifica o listener interno para sincronização imediata
|
||||
if self.notificador:
|
||||
self.notificador.notificar_emissao(id_tecnospeed=id_tecnospeed, documento="nfe", cnpj=id_empresa)
|
||||
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'nfe', data.get('id_usuario', 1), '0', 'ERRO', f"Erro ao cadastrar NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao cadastrar NFe: {e}")
|
||||
|
||||
def cancelar_nfe(self, id_nota: str, justificativa: str):
|
||||
# Solicita o cancelamento de uma NFe existente mediante uma justificativa
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/{id_nota}/cancelamento", {"justificativa": justificativa})
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DELETE', 'nfe', 1, id_nota, 'SUCESSO', 'NFe cancelada com sucesso', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DELETE', 'nfe', 1, id_nota, 'ERRO', f"Erro ao cancelar NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao cancelar NFe: {e}")
|
||||
|
||||
def enviar_cce(self, id_nota: str, correcao: str):
|
||||
# Envia uma Carta de Correção Eletrônica (CCe) para retificar uma NFe já emitida
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/{id_nota}/cce", {"correcao": correcao})
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'nfe', 1, id_nota, 'SUCESSO', 'CCe enviada com sucesso', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'nfe', 1, id_nota, 'ERRO', f"Erro ao enviar CCe NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao enviar CCe NFe: {e}")
|
||||
|
||||
def consultar_resumo(self, id_nota: str):
|
||||
# Consulta o resumo e o status atual da NFe na PlugNotas pelo seu ID
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/resumo")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', 1, id_nota, 'SUCESSO', 'Resumo de NFe consultado', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', 1, id_nota, 'ERRO', f"Erro ao consultar resumo NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao consultar resumo NFe: {e}")
|
||||
|
||||
def consultar_resumo_por_cnpj_integracao(self, cnpj: str, id_integracao: str):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{cnpj}/{id_integracao}/resumo")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_integracao, id_integracao, 'SUCESSO', 'Resumo por CNPJ/Integração consultado', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_integracao, id_integracao, 'ERRO', f"Erro ao consultar resumo NFe por CNPJ/Integração: {e}", "{}"))
|
||||
raise Exception(f"Erro ao consultar resumo NFe por CNPJ/Integração: {e}")
|
||||
|
||||
def consultar_status_cancelamento(self, id_nota: str):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/cancelamento/status")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_nota, id_nota, 'SUCESSO', 'Status de cancelamento consultado', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_nota, id_nota, 'ERRO', f"Erro ao consultar status de cancelamento NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao consultar status de cancelamento NFe: {e}")
|
||||
|
||||
def consultar_status_cce(self, id_nota: str):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/cce/status")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_nota, id_nota, 'SUCESSO', 'Status CCe consultado', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_nota, id_nota, 'ERRO', f"Erro ao consultar status CCe NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao consultar status CCe NFe: {e}")
|
||||
|
||||
def baixar_pdf(self, id_nota: str):
|
||||
# Obtém o link para download ou o stream do PDF da Nota Fiscal processada
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/pdf")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DOWNLOAD (PDF)', 'nfe', id_nota, id_nota, 'SUCESSO', 'PDF baixado com sucesso', json_lib.dumps(resposta) if isinstance(resposta, dict) else '{}'))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DOWNLOAD (PDF)', 'nfe', id_nota, id_nota, 'ERRO', f"Erro ao baixar PDF NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao baixar PDF NFe: {e}")
|
||||
|
||||
def baixar_xml(self, id_nota: str):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/xml")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DOWNLOAD (XML)', 'nfe', id_nota, id_nota, 'SUCESSO', 'XML baixado com sucesso', json_lib.dumps(resposta) if isinstance(resposta, dict) else '{}'))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DOWNLOAD (XML)', 'nfe', id_nota, id_nota, 'ERRO', f"Erro ao baixar XML NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao baixar XML NFe: {e}")
|
||||
70
backup/src/Relatorios/relatorios.py
Normal file
70
backup/src/Relatorios/relatorios.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
from utils.api import Api
|
||||
from utils.banco import Banco
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
# Extração e consumo de relatórios massivos de emissões diretamente da API e consolidações em lotes
|
||||
class Relatorios:
|
||||
def __init__(self, url_base, api_key, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def nfe_emitidas(self, data: dict = None):
|
||||
try:
|
||||
resposta = self.api.get_paginated(f"{self.url}nfe/relatorio")
|
||||
self._log(RealmType.OPERATIONAL, "Relatório de NFe emitidas consultado")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
msg_erro = f"Erro ao consultar relatório NFe emitidas: {e}"
|
||||
self._log(RealmType.ERRORS, msg_erro)
|
||||
return {"error": msg_erro}
|
||||
|
||||
def nfe_por_cnpj(self, cnpj, data: dict = None):
|
||||
try:
|
||||
resposta = self.api.get_paginated(f"{self.url}nfe/{cnpj}/relatorio")
|
||||
self._log(RealmType.OPERATIONAL, f"Relatório de NFe por CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
msg_erro = f"Erro ao consultar relatório NFe por CNPJ: {e}"
|
||||
self._log(RealmType.ERRORS, msg_erro)
|
||||
return {"error": msg_erro}
|
||||
|
||||
def nfse_emitidas(self, data_inicial=None, data_final=None, data: dict = None):
|
||||
try:
|
||||
if not data_inicial:
|
||||
data_inicial = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
|
||||
if not data_final:
|
||||
data_final = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
resposta = self.api.get_paginated(f"{self.url}nfse/relatorio?from={data_inicial}&to={data_final}")
|
||||
self._log(RealmType.OPERATIONAL, f"Relatório NFSe emitidas: {data_inicial} a {data_final}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
msg_erro = f"Erro ao consultar relatório NFSe emitidas: {e}"
|
||||
self._log(RealmType.ERRORS, msg_erro)
|
||||
return {"error": msg_erro}
|
||||
|
||||
def nfse_por_cnpj(self, cnpj, data_inicial=None, data_final=None, data: dict = None):
|
||||
try:
|
||||
if not data_inicial:
|
||||
data_inicial = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
|
||||
if not data_final:
|
||||
data_final = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
resposta = self.api.get_paginated(f"{self.url}nfse/{cnpj}/relatorio?from={data_inicial}&to={data_final}")
|
||||
self._log(RealmType.OPERATIONAL, f"Relatório NFSe por CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
msg_erro = f"Erro ao consultar relatório NFSe por CNPJ: {e}"
|
||||
self._log(RealmType.ERRORS, msg_erro)
|
||||
return {"error": msg_erro}
|
||||
|
||||
165
backup/src/certificado_digital/certificado_digital.py
Normal file
165
backup/src/certificado_digital/certificado_digital.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
from utils.api import Api
|
||||
from utils.banco import Banco, CertificadoDigitalTable
|
||||
from datetime import date
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
import json
|
||||
import base64
|
||||
|
||||
class CertificadoDigital:
|
||||
# Serviço que gerencia o upload e atualização de certificados digitais A1/A3 na plataforma da PlugNotas
|
||||
def __init__(self, url_base, banco: Banco, api_key: str, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key=api_key)
|
||||
self.banco = banco
|
||||
self.url = f"{url_base}/certificado"
|
||||
self.logger = logger
|
||||
|
||||
def post_form_data(self, data):
|
||||
# Realiza o envio multipart/form-data do arquivo físico do certificado .pfx para a API e o salva no banco
|
||||
try:
|
||||
files = {
|
||||
'arquivo': (
|
||||
'certificado.pfx',
|
||||
base64.b64decode(data['arquivo']) if isinstance(data['arquivo'], str) else data['arquivo'],
|
||||
'application/x-pkcs12'
|
||||
),
|
||||
'senha': (None, data['senha'])
|
||||
}
|
||||
if 'email' in data:
|
||||
files['email'] = (None, data['email'])
|
||||
|
||||
response = self.api.post_form_data(self.url, files)
|
||||
id_tecnospeed = response.get('data', {}).get('id')
|
||||
|
||||
id_usr = data.get('id_usuario', 0)
|
||||
id_emp = data.get('id_empresa', "0")
|
||||
|
||||
if not id_tecnospeed:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'certificado_digital', id_usr, '0', 'ERRO', 'ID do certificado não encontrado na resposta da API', json.dumps(response)))
|
||||
raise ValueError("Erro ao obter ID do certificado na resposta da API.")
|
||||
|
||||
expiration_value = data.get('expirationAt') or data.get('data_expiracao')
|
||||
certificado = CertificadoDigitalTable(
|
||||
id_tecnospeed=id_tecnospeed,
|
||||
certificado=data['arquivo'],
|
||||
expirationAt=date.fromisoformat(expiration_value) if expiration_value else date.today(),
|
||||
senha=data['senha'],
|
||||
status=True,
|
||||
id_empresa=id_emp,
|
||||
id_usuario=id_usr,
|
||||
email=data.get('email')
|
||||
)
|
||||
|
||||
self.banco.add(certificado)
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'certificado_digital', id_usr, id_tecnospeed, 'SUCESSO', 'Certificado digital cadastrado com sucesso', json.dumps(response)))
|
||||
|
||||
return id_tecnospeed
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'certificado_digital', data.get('id_usuario', 0), '0', 'ERRO', f"Erro ao adicionar certificado: {e}", "{}"))
|
||||
raise
|
||||
|
||||
def contar_vencidos(self, lista_certificados):
|
||||
# Auxiliar para contar certificados ativos e vencidos na listagem
|
||||
if isinstance(lista_certificados, dict) and "data" in lista_certificados:
|
||||
lista = lista_certificados["data"]
|
||||
elif isinstance(lista_certificados, list):
|
||||
lista = lista_certificados
|
||||
else:
|
||||
return {"certificados_ativos": 0, "certificados_vencidos": 0}
|
||||
|
||||
ativos = 0
|
||||
vencidos = 0
|
||||
hoje = date.today()
|
||||
|
||||
for cert in lista:
|
||||
if not isinstance(cert, dict): continue
|
||||
|
||||
# Tenta encontrar o campo de expiração (vencimento ou outros nomes conhecidos)
|
||||
exp_str = cert.get("vencimento") or cert.get("expirationAt") or cert.get("dataVencimento") or cert.get("data_expiracao")
|
||||
|
||||
if exp_str:
|
||||
try:
|
||||
# Tenta converter a string de data:
|
||||
# 1. Formato DD/MM/YYYY (do snipped do usuário)
|
||||
# 2. Formato ISO YYYY-MM-DD
|
||||
if "/" in exp_str:
|
||||
partes = exp_str.split(" ")[0].split("/")
|
||||
if len(partes) == 3:
|
||||
data_exp = date(int(partes[2]), int(partes[1]), int(partes[0]))
|
||||
else:
|
||||
data_exp = date.fromisoformat(exp_str[:10])
|
||||
|
||||
if data_exp >= hoje:
|
||||
ativos += 1
|
||||
else:
|
||||
vencidos += 1
|
||||
except:
|
||||
continue
|
||||
|
||||
return {
|
||||
"certificados_ativos": ativos,
|
||||
"certificados_vencidos": vencidos
|
||||
}
|
||||
|
||||
def get(self, id=None):
|
||||
# Consulta a situação de um certificado digital específico pelo ID ou lista todos vinculados
|
||||
try:
|
||||
if id:
|
||||
resultado = self.api.get(f"{self.url}/{id}")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'certificado_digital', id, id, 'SUCESSO', f"Certificado consultado - ID: {id}", json.dumps(resultado)))
|
||||
return resultado
|
||||
else:
|
||||
resposta = self.api.get(self.url)
|
||||
|
||||
# Calcula totais
|
||||
totais = self.contar_vencidos(resposta)
|
||||
|
||||
# Organiza a resposta unificada
|
||||
resultado = {
|
||||
"certificados": resposta if isinstance(resposta, list) else resposta.get("data", []),
|
||||
"totais": totais
|
||||
}
|
||||
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'certificado_digital', 'GERAL', '0', 'SUCESSO', 'Listagem de certificados digitais', json.dumps(resultado)))
|
||||
return resultado
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'certificado_digital', id or 'GERAL', '0', 'ERRO', f"Erro ao buscar certificado: {e}", "{}"))
|
||||
raise
|
||||
|
||||
def put_certificado(self, id, data):
|
||||
# Atualiza o arquivo físico ou a senha de um certificado já existente
|
||||
try:
|
||||
certificado = self.banco.session.query(CertificadoDigitalTable).filter_by(id_tecnospeed=id).first()
|
||||
id_usr = data.get('id_usuario', 0)
|
||||
|
||||
if not certificado:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'certificado_digital', id_usr, id, 'ERRO', f"Certificado com ID {id} não encontrado", "{}"))
|
||||
raise ValueError(f"Certificado com ID {id} não encontrado no banco.")
|
||||
|
||||
files = {
|
||||
'arquivo': (
|
||||
'certificado.pfx',
|
||||
base64.b64decode(data['arquivo']) if isinstance(data['arquivo'], str) else data['arquivo'],
|
||||
'application/x-pkcs12'
|
||||
),
|
||||
'senha': (None, data['senha'])
|
||||
}
|
||||
if 'email' in data:
|
||||
files['email'] = (None, data['email'])
|
||||
|
||||
self.api.put_form_data(f"{self.url}/{id}", files)
|
||||
|
||||
certificado.certificado = data['arquivo']
|
||||
certificado.senha = data['senha']
|
||||
expiration_value = data.get('expirationAt') or data.get('data_expiracao')
|
||||
if expiration_value:
|
||||
certificado.expirationAt = date.fromisoformat(expiration_value)
|
||||
certificado.updatedAt = date.today()
|
||||
|
||||
self.banco.update(certificado)
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'certificado_digital', id_usr, id, 'SUCESSO', 'Certificado digital atualizado com sucesso', json.dumps({"id": id})))
|
||||
|
||||
return id
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'certificado_digital', data.get('id_usuario', 0), id, 'ERRO', f"Erro ao atualizar certificado: {e}", "{}"))
|
||||
raise
|
||||
177
backup/src/empresas/empresa.py
Normal file
177
backup/src/empresas/empresa.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
from Models.empresa import Empresa
|
||||
from utils.api import Api
|
||||
from utils.banco import Banco, EnterpriseTable, LogoTable
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
import json
|
||||
import string
|
||||
import random
|
||||
import time
|
||||
|
||||
def generate_cuid():
|
||||
"""Gera um ID no formato aproximado de CUID (c + timestamp + random)."""
|
||||
ts = hex(int(time.time() * 1000))[2:]
|
||||
rand = ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
||||
return f"c{ts}{rand}"[:25]
|
||||
|
||||
# Gerenciador de contas de Empresas emissoras de notas e seus respectivos certificados e logos
|
||||
class EmpresaController:
|
||||
def __init__(self, url_base, api_key: str, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key=api_key)
|
||||
self.banco = banco
|
||||
self.url = f"{url_base}/empresa"
|
||||
self.logger = logger
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def post(self, empresa: Empresa, data: dict):
|
||||
try:
|
||||
dict_api = empresa.model_dump(exclude={'id', 'id_empresa', 'banco', 'email'}, exclude_none=True)
|
||||
|
||||
# Injeção das configurações fixas e obrigatórias para a PlugNotas
|
||||
if dict_api.get("nfe") and dict_api["nfe"].get("ativo"):
|
||||
nfe_config_front = dict_api["nfe"].get("config") or {}
|
||||
dict_api["nfe"]["config"] = {
|
||||
"producao": False,
|
||||
"email": {"envio": False},
|
||||
"dfe": {"primeiroNsu": False},
|
||||
"impressaoFcp": False,
|
||||
"impressaoPartilha": False,
|
||||
**nfe_config_front
|
||||
}
|
||||
dict_api["nfe"]["tipoContrato"] = 0
|
||||
|
||||
if dict_api.get("nfse") and dict_api["nfse"].get("ativo"):
|
||||
nfse_config_front = dict_api["nfse"].get("config") or {}
|
||||
dict_api["nfse"]["config"] = {
|
||||
"producao": False,
|
||||
"email": {"envio": False},
|
||||
"rps": {"lote": 1, "numeracao": [{"numero": 1, "serie": "1"}]},
|
||||
"prefeitura": {"login": "12345", "senha": "123"},
|
||||
**nfse_config_front
|
||||
}
|
||||
dict_api["nfse"]["tipoContrato"] = 0
|
||||
print("===========================")
|
||||
print("ENVIANDO PARA A API:", json.dumps(dict_api, indent=2))
|
||||
print("===========================")
|
||||
retorno = self.api.post(data=dict_api, url=self.url)
|
||||
if 'error' in retorno:
|
||||
err_data = retorno.get('response', {}).get('error', {})
|
||||
msg = err_data.get('message', 'Erro desconhecido')
|
||||
detalhes = err_data.get('data', [])
|
||||
if detalhes:
|
||||
msg += f" Detalhes: {detalhes}"
|
||||
|
||||
print(f"\n[ERRO API PLUGNOTAS] {msg}\n", flush=True)
|
||||
self._log(RealmType.ERRORS, f"Erro na API ao cadastrar empresa: {msg}")
|
||||
raise Exception(msg)
|
||||
|
||||
# Gerar ID CUID se não vier no objeto ou se for nulo
|
||||
empresa_id = empresa.id if empresa.id else generate_cuid()
|
||||
|
||||
empresa_nova = EnterpriseTable(
|
||||
id=empresa_id,
|
||||
cnpj=empresa.cpfCnpj,
|
||||
name=empresa.razaoSocial,
|
||||
banco=empresa.banco,
|
||||
email=empresa.email,
|
||||
status=True
|
||||
)
|
||||
self.banco.add(empresa_nova)
|
||||
self._log(RealmType.OPERATIONAL, f"Empresa cadastrada com sucesso - CNPJ: {empresa.cpfCnpj} (ID: {empresa_id})")
|
||||
|
||||
if isinstance(retorno, dict) and 'data' in retorno:
|
||||
retorno['data']['id'] = empresa_nova.id
|
||||
retorno['data']['id_plugnotas'] = retorno['data'].get('_id')
|
||||
retorno['data']['name'] = empresa_nova.name
|
||||
retorno['data']['status'] = empresa_nova.status
|
||||
retorno['data'].pop('razaoSocial', None)
|
||||
retorno['data'].pop('id_empresa', None)
|
||||
|
||||
return retorno
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro no banco ao cadastrar empresa: {e}")
|
||||
return {"error": f"Erro ao cadastrar empresa: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def get(self, cnpj: str = None, data: dict = None):
|
||||
try:
|
||||
if cnpj:
|
||||
resposta = self.api.get(f"{self.url}/{cnpj}")
|
||||
|
||||
if isinstance(resposta, dict) and "error" not in resposta:
|
||||
empresa_local = self.banco.session.query(EnterpriseTable).filter_by(cnpj=cnpj).first()
|
||||
if empresa_local:
|
||||
resposta["id"] = empresa_local.id
|
||||
resposta["name"] = empresa_local.name
|
||||
resposta["status"] = empresa_local.status
|
||||
resposta.pop("razaoSocial", None)
|
||||
|
||||
if "_id" in resposta:
|
||||
resposta["id_plugnotas"] = resposta["_id"]
|
||||
|
||||
else:
|
||||
resposta = self.api.get_paginated(self.url)
|
||||
# Injetar o ID do banco local em cada empresa listada
|
||||
if isinstance(resposta, dict) and "data" in resposta:
|
||||
for emp in resposta["data"]:
|
||||
empresa_local = self.banco.session.query(EnterpriseTable).filter_by(cnpj=emp.get("cpfCnpj")).first()
|
||||
if empresa_local:
|
||||
emp["id"] = empresa_local.id
|
||||
emp["name"] = empresa_local.name
|
||||
emp["status"] = empresa_local.status
|
||||
emp.pop("razaoSocial", None)
|
||||
if "_id" in emp:
|
||||
emp["id_plugnotas"] = emp["_id"]
|
||||
|
||||
self._log(RealmType.OPERATIONAL, f"Empresa consultada - CNPJ: {cnpj or 'TODAS'}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao buscar empresa: {e}")
|
||||
return {"error": f"Erro ao buscar empresa: {str(e)}"}
|
||||
|
||||
def post_file(self, file, cnpj, data=None):
|
||||
try:
|
||||
empresa = self.banco.session.query(EnterpriseTable).filter_by(cnpj=cnpj).first()
|
||||
if not empresa:
|
||||
self._log(RealmType.ERRORS, f"Empresa não encontrada para upload de logo - CNPJ: {cnpj}")
|
||||
return {"error": "Empresa não encontrada"}
|
||||
|
||||
logo = LogoTable(
|
||||
id_empresa=empresa.id,
|
||||
logo=file,
|
||||
status=True
|
||||
)
|
||||
self.banco.add(logo)
|
||||
|
||||
files = {'arquivo': ('logo.png', file, 'image/png')}
|
||||
resposta = self.api.post_file(file=files, url=f"{self.url}/{cnpj}/logotipo")
|
||||
self._log(RealmType.OPERATIONAL, f"Logo enviada com sucesso - CNPJ: {cnpj}")
|
||||
return resposta
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro no banco ao salvar logo: {e}")
|
||||
return {"error": f"Erro ao salvar logo: {str(e)}"}
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro inesperado ao enviar logo: {e}")
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def patch_certificado(self, cnpj: str, certificado: str, data: dict):
|
||||
try:
|
||||
empresa = self.banco.session.query(EnterpriseTable).filter_by(cnpj=cnpj).first()
|
||||
if not empresa:
|
||||
self._log(RealmType.ERRORS, f"Empresa não encontrada para atualizar certificado - CNPJ: {cnpj}")
|
||||
return {"error": "Empresa não encontrada"}
|
||||
resposta = self.api.patch(url=f"{self.url}/{cnpj}/certificado", data={"certificado": certificado})
|
||||
self._log(RealmType.OPERATIONAL, f"Certificado da empresa atualizado - CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao atualizar certificado: {e}")
|
||||
return {"error": f"Erro ao atualizar certificado: {str(e)}"}
|
||||
|
||||
173
backup/src/listenerEndpoint.py
Normal file
173
backup/src/listenerEndpoint.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
import uvicorn
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import RPCExchange2, LoggerExchange2, AppIC2Builder
|
||||
from utils.exchangeModels import RealmType
|
||||
|
||||
|
||||
# Campos obrigatórios que o PlugNotas envia no Endpoint
|
||||
CAMPOS_OBRIGATORIOS = ["id", "status", "documento"]
|
||||
|
||||
# Status válidos que o PlugNotas envia
|
||||
STATUS_VALIDOS = ["CONCLUIDO", "REJEITADO", "CANCELADO"]
|
||||
|
||||
# Tipos de documento/entidades aceitos
|
||||
DOCUMENTOS_VALIDOS = ["nfe", "nfse", "certificado_digital"]
|
||||
|
||||
# Variáveis globais
|
||||
redis_client = None
|
||||
system_config = None
|
||||
exchange = None
|
||||
logger = None
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def ciclo_app(app: FastAPI):
|
||||
# Gerencia o ciclo de vida da aplicação, inicializando conexões com Redis e RabbitMQ (Exchange) na inicialização
|
||||
global redis_client, system_config, exchange
|
||||
|
||||
try:
|
||||
redis_client = RedisClient(host=os.getenv('REDIS_HOST', 'localhost'), port=6379)
|
||||
system_config_data = redis_client.get("system_config")
|
||||
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis.")
|
||||
else:
|
||||
system_config = SystemConfig(**json.loads(system_config_data))
|
||||
|
||||
builder = AppIC2Builder(service_name="tecnospeed", versionstr="2.0.1", origin=system_config.origin)
|
||||
global logger
|
||||
logger = LoggerExchange2(ic2_builder=builder, amqps=system_config.amqps, system="integracao", service="tecnospeed", version="2.0.1", origin=system_config.origin)
|
||||
logger.logIC2(RealmType.LOGS, (f"[{system_config.origin}] Configuração lida com sucesso pelo Listener Endpoint.",))
|
||||
|
||||
exchange = RPCExchange2(
|
||||
amqps=system_config.amqps,
|
||||
origin=system_config.origin,
|
||||
system="integracao",
|
||||
service="controller",
|
||||
version="2.0.1",
|
||||
client=""
|
||||
)
|
||||
servicekey = system_config.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(serviceid=system_config.serviceid, servicekey=servicekey)
|
||||
logger.logIC2(RealmType.LOGS, ("Exchange inicializado com sucesso para o Listener Endpoint.",))
|
||||
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.logIC2(RealmType.ERRORS, (f"Erro ao inicializar o Listener Endpoint: {e}",))
|
||||
else:
|
||||
print(f"Erro ao inicializar o Listener Endpoint: {e}")
|
||||
|
||||
yield # Aplicação rodando
|
||||
|
||||
if logger:
|
||||
logger.logIC2(RealmType.LOGS, ("Listener Endpoint encerrando...",))
|
||||
logger.close()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Listener Endpoint PlugNotas",
|
||||
description="Endpoint para receber notificações Endpoint do PlugNotas",
|
||||
version="2.0.1",
|
||||
lifespan=ciclo_app
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def healthy():
|
||||
# Verifica a saúde da aplicação, validando se as dependências (Redis e Exchange) estão conectadas
|
||||
status = {
|
||||
"status": "ok",
|
||||
"redis": redis_client is not None,
|
||||
"exchange": exchange is not None,
|
||||
"system_config": system_config is not None
|
||||
}
|
||||
if not exchange:
|
||||
return JSONResponse(status_code=503, content={**status, "status": "degraded", "message": "Exchange não inicializado"})
|
||||
return status
|
||||
|
||||
|
||||
@app.post("/listenerPlugnotas")
|
||||
@app.post("/listenerPlugnotas/{cnpj}")
|
||||
async def receber_Endpoint_plugnotas(request: Request, cnpj: str = None):
|
||||
# Rota principal que recebe a notificação da PlugNotas e repassa para processamento no RabbitMQ
|
||||
try:
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception as e:
|
||||
logger.logIC2(RealmType.ERRORS, (f"Payload inválido recebido (não é JSON válido): {e}",))
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"status": "error", "message": "Payload não é um JSON válido"}
|
||||
)
|
||||
|
||||
documento = payload.get("documento", "desconhecido")
|
||||
status_nota = payload.get("status", "desconhecido")
|
||||
id_nota = payload.get("id", "")
|
||||
id_integracao = payload.get("idIntegracao", "")
|
||||
emitente = payload.get("emitente", cnpj or "")
|
||||
|
||||
logger.logIC2(RealmType.LOGS, (
|
||||
f"Endpoint recebido - Documento: {documento} | Status: {status_nota} | "
|
||||
f"ID: {id_nota} | IDIntegracao: {id_integracao} | Emitente: {emitente}",
|
||||
))
|
||||
|
||||
campos_faltantes = [campo for campo in CAMPOS_OBRIGATORIOS if campo not in payload]
|
||||
if campos_faltantes:
|
||||
logger.logIC2(RealmType.ERRORS, (
|
||||
f"Endpoint com campos obrigatórios faltantes: {campos_faltantes} | "
|
||||
f"Payload recebido: {json.dumps(payload)}",
|
||||
))
|
||||
return {"status": "warning", "message": f"Campos faltantes: {campos_faltantes}"}
|
||||
|
||||
if documento not in DOCUMENTOS_VALIDOS:
|
||||
logger.logIC2(RealmType.ERRORS, (
|
||||
f"Tipo de documento desconhecido: {documento} | Payload: {json.dumps(payload)}",
|
||||
))
|
||||
if status_nota not in STATUS_VALIDOS:
|
||||
logger.logIC2(RealmType.ERRORS, (
|
||||
f"Status desconhecido: {status_nota} | Documento: {documento} | ID: {id_nota}",
|
||||
))
|
||||
|
||||
if not exchange:
|
||||
logger.logIC2(RealmType.ERRORS, ("Exchange não inicializado no momento da chegada do Endpoint.",))
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": "Serviço temporariamente indisponível"}
|
||||
)
|
||||
|
||||
# Envia o EXATO objeto dict recebido do PlugNotas
|
||||
exchange.setPayload(json.dumps(payload))
|
||||
|
||||
# Chama a rota RPC aguardando retorno
|
||||
try:
|
||||
resposta_rpc = exchange.callRPC("Endpoint PlugNotas Recebido", timeout=15)
|
||||
|
||||
logger.logIC2(RealmType.LOGS, (
|
||||
f"Endpoint enviado ao RPC Controller. "
|
||||
f"Documento: {documento} | Status: {status_nota} | ID: {id_nota} | Resposta: {resposta_rpc}",
|
||||
))
|
||||
|
||||
return {"status": "ok", "message": "Enviado ao Controller com sucesso", "controller_response": resposta_rpc}
|
||||
|
||||
except Exception as e:
|
||||
logger.logIC2(RealmType.ERRORS, (f"Erro ou Timeout ao conversar com o Controller (RPC): {e}",))
|
||||
return JSONResponse(
|
||||
status_code=502,
|
||||
content={"status": "error", "message": "Falha na comunicação RPC com o Controller"}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.logIC2(RealmType.ERRORS, (f"Erro inesperado no Listener Endpoint: {e}",))
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": "Erro interno no processamento do Endpoint"}
|
||||
)
|
||||
24
backup/src/logs/integracao_01
Normal file
24
backup/src/logs/integracao_01
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
2026-04-01 09:09:33,082 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-01 09:09:33.082579-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-01 09:09:33,700 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-01 09:09:33.700242-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-01 09:14:24,402 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-01 09:14:24.402193-03:00',
|
||||
'msg': 'Listener Webhook encerrando...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
194
backup/src/logs/integracao_02
Normal file
194
backup/src/logs/integracao_02
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
2026-04-02 09:14:16,362 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 09:14:16.362304-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 09:14:16,932 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 09:14:16.932459-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:14:30,014 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:14:30.013914-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:14:31,040 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:14:31.039753-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:22:20,024 - integracao - INFO - { 'caller': 'receber_webhook_plugnotas',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:22:20.024167-03:00',
|
||||
'msg': 'Webhook recebido - Documento: nfe | Status: CONCLUIDO | ID: '
|
||||
'69cab453... | IDIntegracao: | Emitente:',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:22:20,263 - integracao - INFO - { 'caller': 'receber_webhook_plugnotas',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:22:20.263204-03:00',
|
||||
'msg': 'Webhook repassado ao worker com sucesso - Documento: nfe | Status: '
|
||||
'CONCLUIDO | ID: 69cab453...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:23:45,608 - integracao - INFO - { 'caller': 'receber_webhook_plugnotas',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:23:45.608300-03:00',
|
||||
'msg': 'Webhook recebido - Documento: nfe | Status: CONCLUIDO | ID: '
|
||||
'69cab45329f21a7de7691961 | IDIntegracao: XXXYY99999_TESTE445 | '
|
||||
'Emitente: 08187168000160',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:23:46,107 - integracao - INFO - { 'caller': 'receber_webhook_plugnotas',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:23:46.107170-03:00',
|
||||
'msg': 'Webhook repassado ao worker com sucesso - Documento: nfe | Status: '
|
||||
'CONCLUIDO | ID: 69cab45329f21a7de7691961',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:26:07,402 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:26:07.401683-03:00',
|
||||
'msg': 'Listener Webhook encerrando...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:36,191 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:36.190896-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:36,969 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:36.969622-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:37,459 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:37.459047-03:00',
|
||||
'msg': 'Listener Webhook encerrando...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:42,582 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:42.582792-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:43,391 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:43.391071-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:43,782 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:43.781995-03:00',
|
||||
'msg': 'Listener Webhook encerrando...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:03:48,010 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:03:48.009993-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:03:48,217 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:03:48.216535-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:17,812 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:17.812369-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:18,488 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:18.487913-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:18,864 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:18.863889-03:00',
|
||||
'msg': 'Erro fatal ao conectar ao RabbitMQ - VERIFY PARAMETERS: '
|
||||
'is_amqp_url: True\n'
|
||||
' amqps[amqp://guest:guest@localhost:5672/]\n'
|
||||
' origin[local]\n'
|
||||
' system[integracao]\n'
|
||||
' service[tecnospeed]\n'
|
||||
' version[2.0.1\n'
|
||||
' client[]',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:30,426 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:30.426681-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:30,947 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:30.947199-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
56
backup/src/logs/integracao_06
Normal file
56
backup/src/logs/integracao_06
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
2026-04-06 08:25:17,847 - teste_wrapper.integracao - INFO - params: <URLParameters host=jackal.rmq.cloudamqp.com port=5671 virtual_host=iawkzmhh ssl=True> amqps: amqps://iawkzmhh:4iIVXCgLDsLy2oVyVWeV-o6shg-BZ89z@jackal.rmq.cloudamqp.com/iawkzmhh
|
||||
2026-04-06 08:25:18,327 - teste_wrapper.integracao - INFO - Logger Messages topics: logs.teste_wrapper.integracao.
|
||||
2026-04-06 08:25:18,361 - teste_wrapper.integracao - INFO - {'caller': 'IC2',
|
||||
'client': '',
|
||||
'hmac': '862c786853960277c4d518d1eb01143b2fe7782092dbf5403fb61b52127c7829',
|
||||
'logDate': '2026-04-06 08:25:18.361486-03:00',
|
||||
'msg': "LOGIC2 LOGS [('Testando o novo fo ...]",
|
||||
'origin': '',
|
||||
'payload': '44102feb05c76aac7b2b',
|
||||
'service': 'teste_wrapper',
|
||||
'serviceid': 'AF1699F719F1B6C81DE2BD38AB811994E3089B35C8E9314813A770AC7AE25D8D',
|
||||
'system': 'integracao',
|
||||
'target': '',
|
||||
'type': 'logger',
|
||||
'version': ''}
|
||||
2026-04-06 08:25:18,368 - teste_wrapper.integracao - INFO - {'caller': 'logMsg',
|
||||
'client': '',
|
||||
'hmac': '',
|
||||
'logDate': '2026-04-06 08:25:18.367868-03:00',
|
||||
'msg': 'Testando o novo formato IC2',
|
||||
'origin': '',
|
||||
'payload': '',
|
||||
'service': 'teste_wrapper',
|
||||
'serviceid': 'AF1699F719F1B6C81DE2BD38AB811994E3089B35C8E9314813A770AC7AE25D8D',
|
||||
'system': 'integracao',
|
||||
'target': '',
|
||||
'type': 'logger',
|
||||
'version': ''}
|
||||
2026-04-06 08:28:37,324 - teste_wrapper.integracao - INFO - params: <URLParameters host=jackal.rmq.cloudamqp.com port=5671 virtual_host=iawkzmhh ssl=True> amqps: amqps://iawkzmhh:4iIVXCgLDsLy2oVyVWeV-o6shg-BZ89z@jackal.rmq.cloudamqp.com/iawkzmhh
|
||||
2026-04-06 08:28:37,834 - teste_wrapper.integracao - INFO - Logger Messages topics: logs.teste_wrapper.integracao.
|
||||
2026-04-06 08:28:37,855 - teste_wrapper.integracao - INFO - {'caller': 'IC2',
|
||||
'client': '',
|
||||
'hmac': '841681411efce4ae99ff2602ae79da6284e6c3ca247a27da3a87dedf3771244e',
|
||||
'logDate': '2026-04-06 08:28:37.855637-03:00',
|
||||
'msg': "LOGIC2 LOGS [('Testando o formato ...]",
|
||||
'origin': '',
|
||||
'payload': '353fd214129406d78301',
|
||||
'service': 'teste_wrapper',
|
||||
'serviceid': 'AF1699F719F1B6C81DE2BD38AB811994E3089B35C8E9314813A770AC7AE25D8D',
|
||||
'system': 'integracao',
|
||||
'target': '',
|
||||
'type': 'logger',
|
||||
'version': ''}
|
||||
2026-04-06 08:28:37,865 - teste_wrapper.integracao - INFO - {'caller': 'logMsg',
|
||||
'client': '',
|
||||
'hmac': '',
|
||||
'logDate': '2026-04-06 08:28:37.865799-03:00',
|
||||
'msg': 'Testando o formato IC2',
|
||||
'origin': '',
|
||||
'payload': '',
|
||||
'service': 'teste_wrapper',
|
||||
'serviceid': 'AF1699F719F1B6C81DE2BD38AB811994E3089B35C8E9314813A770AC7AE25D8D',
|
||||
'system': 'integracao',
|
||||
'target': '',
|
||||
'type': 'logger',
|
||||
'version': ''}
|
||||
0
backup/src/logs/integracao_25
Normal file
0
backup/src/logs/integracao_25
Normal file
0
backup/src/logs/integracao_26
Normal file
0
backup/src/logs/integracao_26
Normal file
140
backup/src/logs/integracao_27
Normal file
140
backup/src/logs/integracao_27
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
2026-03-27 14:57:57,807 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-03-27 14:57:57.807604-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 14:57:58,282 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-03-27 14:57:58.282491-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 14:59:07,273 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 14:59:07.273291-03:00',
|
||||
'msg': "Configuração recebida - {'api_key': None, 'x_api_key': "
|
||||
"'2da392a6-79d2-4304-a8b7-959572c7e44d', 'user_postgres': "
|
||||
"'postgres', 'pass_postgres': 'postgres', 'host_postgres': "
|
||||
"'localhost', 'port_postgres': '5432', 'database_postgres': "
|
||||
"'tecnospeed'}",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 14:59:08,015 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 14:59:08.014993-03:00',
|
||||
'msg': 'Configuração incompleta',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:00:56,891 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:00:56.890566-03:00',
|
||||
'msg': "Configuração recebida - {'api_key': None, 'x_api_key': "
|
||||
"'2da392a6-79d2-4304-a8b7-959572c7e44d', 'user_postgres': "
|
||||
"'postgres', 'pass_postgres': 'postgres', 'host_postgres': "
|
||||
"'localhost', 'port_postgres': '5432', 'database_postgres': "
|
||||
"'sg_servico_tecnospeed'}",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:00:57,787 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:00:57.787472-03:00',
|
||||
'msg': 'Configuração incompleta',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:02:14,102 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:02:14.101859-03:00',
|
||||
'msg': "Configuração recebida - {'api_key': None, 'x_api_key': "
|
||||
"'2da392a6-79d2-4304-a8b7-959572c7e44d', 'user_postgres': "
|
||||
"'postgres', 'pass_postgres': 'postgres', 'host_postgres': "
|
||||
"'localhost', 'port_postgres': '5432', 'database_postgres': "
|
||||
"'tecnospeed'}",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:02:14,792 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:02:14.791942-03:00',
|
||||
'msg': 'Configuração incompleta',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:33,329 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-03-27 15:09:33.329870-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:33,698 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-03-27 15:09:33.698183-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:39,159 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:39.158573-03:00',
|
||||
'msg': "Configuração recebida - {'api_key': None, 'x_api_key': "
|
||||
"'2da392a6-79d2-4304-a8b7-959572c7e44d', 'user_postgres': "
|
||||
"'postgres', 'pass_postgres': 'postgres', 'host_postgres': "
|
||||
"'localhost', 'port_postgres': '5432', 'database_postgres': "
|
||||
"'tecnospeed'}",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:44,975 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:44.975168-03:00',
|
||||
'msg': 'Configuração completa para o cliente Test',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:45,763 - integracao - INFO - { 'caller': 'nfe',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:45.763614-03:00',
|
||||
'msg': 'Iniciando serviço de NFe - Método: GET',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:46,610 - integracao - INFO - { 'caller': 'nfe',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:46.610671-03:00',
|
||||
'msg': 'Consultando resumo de NFe',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:47,761 - integracao - INFO - { 'caller': 'nfe',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:47.761417-03:00',
|
||||
'msg': 'Operação realizada com sucesso',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
686
backup/src/main.py
Normal file
686
backup/src/main.py
Normal file
|
|
@ -0,0 +1,686 @@
|
|||
# IMPORT DAS CLASSES DE TRABALHO
|
||||
from certificado_digital.certificado_digital import CertificadoDigital
|
||||
from empresas.empresa import EmpresaController
|
||||
from NFSe.nfse import NFSeService
|
||||
from NFSe.Servico.servico import ServicoController
|
||||
from NFSe.Tomador.tomador import TomadorController
|
||||
from Relatorios.relatorios import Relatorios
|
||||
from webhooks.webhooks import Webhooks
|
||||
from NFe.registroNFe import RegistroNFeService
|
||||
|
||||
# IMPORTS DE MODELS
|
||||
from Models.config import Config, SystemConfig
|
||||
from Models.empresa import Empresa
|
||||
from Models.nfse import NFSe
|
||||
from Models.servico import Servico
|
||||
from Models.nfe import NFe
|
||||
from Models.tomador import Tomador
|
||||
from Models.payload import validate_payload
|
||||
|
||||
# IMPORTS DE BIBLIOTECAS
|
||||
from pydantic import ValidationError
|
||||
import json, sys, time, random, os, uuid
|
||||
|
||||
# IMPORTS DE CLASSES AUXILIARES
|
||||
from utils.Exchange2 import RPCExchange2, LoggerExchange2, AppIC2Builder
|
||||
from utils.exchangeModels import RealmType
|
||||
from utils.banco import Banco
|
||||
|
||||
from utils.redis import RedisClient
|
||||
|
||||
# VERSÃO DO SISTEMA
|
||||
version = "1.0.0" # Versão inicial do sistema
|
||||
version = "1.0.1" # Implementando ORM
|
||||
version = "1.0.2" # Implementando RPC
|
||||
version = "1.0.3" # Implementando configuração via Redis
|
||||
version = "2.0.0" # NFe e refatoração da Main
|
||||
version = "2.0.1" # Refatoração para RPCExchange
|
||||
|
||||
|
||||
class Main:
|
||||
def __init__(self):
|
||||
self.client = "#"
|
||||
self.api_key = ""
|
||||
self.exchange = None
|
||||
self.banco = None
|
||||
self.system_config: SystemConfig = None
|
||||
self.redis = RedisClient(host=os.getenv('REDIS_HOST', 'localhost'), port=6379)
|
||||
self.service = "tecnospeed"
|
||||
self.system = "integracao"
|
||||
self.version = version
|
||||
self.logger = None
|
||||
self.url_base = os.getenv('PLUGNOTAS_URL', 'https://api.sandbox.plugnotas.com.br')
|
||||
self.funcoes = {
|
||||
"nfe": self.nfe,
|
||||
"nfse": self.nfse,
|
||||
"certificado_digital": self.certificado_digital,
|
||||
"empresa": self.empresa,
|
||||
"servico": self.servico,
|
||||
"tomador": self.tomador,
|
||||
"relatorios": self.relatorios,
|
||||
"webhooks": self.webhooks
|
||||
}
|
||||
|
||||
# Métodos de serviços orientado a eventos.
|
||||
|
||||
def nfe(self, metodo, dados=None, correlation_id=None):
|
||||
# Roteador de emissões, baixas e cancelamentos via NFe
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de NFe - Método: {metodo.upper()}",))
|
||||
|
||||
nfe_service = RegistroNFeService(api_key=self.api_key,
|
||||
url_base=self.url_base,
|
||||
banco=self.banco,
|
||||
logger=self.logger)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando NFe",))
|
||||
nfe = NFe(**dados.get('nfe', dados))
|
||||
resultado = nfe_service.cadastrar_nfe(nfe_list=[nfe], data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando resumo de NFe",))
|
||||
resultado = nfe_service.consultar_resumo(id_nota=dados.get('id'))
|
||||
|
||||
case "download":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando PDF NFe",))
|
||||
resultado = nfe_service.baixar_pdf(id_nota=dados.get('id'))
|
||||
|
||||
case "download_xml":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando XML NFe",))
|
||||
resultado = nfe_service.baixar_xml(id_nota=dados.get('id'))
|
||||
|
||||
case "cancelar":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cancelando NFe",))
|
||||
resultado = nfe_service.cancelar_nfe(id_nota=dados.get('id'), justificativa=dados.get('justificativa', 'Cancelamento padrão'))
|
||||
|
||||
case "status_cancelamento":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando status de cancelamento",))
|
||||
resultado = nfe_service.consultar_status_cancelamento(id_nota=dados.get('id'))
|
||||
|
||||
case "cce":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Enviando Carta de Correção NFe",))
|
||||
resultado = nfe_service.enviar_cce(id_nota=dados.get('id'), correcao=dados.get('correcao'))
|
||||
|
||||
case "status_cce":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando status CCe NFe",))
|
||||
resultado = nfe_service.consultar_status_cce(id_nota=dados.get('id'))
|
||||
|
||||
case "resumo":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando resumo de NFe por CNPJ de integracao",))
|
||||
resultado = nfe_service.consultar_resumo_por_cnpj_integracao(cnpj=dados.get('cnpj'), id_integracao=dados.get('id_integracao', dados.get('id')))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='nfe', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
msg = resultado["error"]
|
||||
if "response" in resultado:
|
||||
msg = f"{msg} | Detalhes: {resultado['response']}"
|
||||
raise Exception(msg)
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='nfe', status='SUCESSO', mensagem='Operação concluída', json=json.dumps(resultado) if resultado else '{}', cliente=self.client, correlation_id=correlation_id)
|
||||
return self._responder("ok", resultado)
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'NFE', tabela='nfe', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def nfse(self, metodo, dados=None, correlation_id=None):
|
||||
# Roteador de Notas Fiscais de Roteiro/Serviço (NFSe)
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de NFSe - Método: {metodo.upper()}",))
|
||||
|
||||
nfse_service = NFSeService(api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
url_base=self.url_base,
|
||||
logger=self.logger)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando NFSe",))
|
||||
nfse = NFSe(**dados)
|
||||
resultado = nfse_service.cadastrar_nfse(nfse=nfse, data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando NFSe",))
|
||||
resultado = nfse_service.buscar_nfse(id_nfse=dados.get('id')) if dados and 'id' in dados else nfse_service.listar_nfse()
|
||||
|
||||
case "download":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando NFSe",))
|
||||
resultado = nfse_service.baixar_nfse(id_nfse=dados.get('id'))
|
||||
|
||||
case "download_xml":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando XML NFSe",))
|
||||
resultado = nfse_service.baixar_xml_nfse(id_nfse=dados.get('id'))
|
||||
|
||||
case "cancelar":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cancelando NFSe",))
|
||||
resultado = nfse_service.solicitar_cancelamento_nfse(id_nfse=dados.get('id'))
|
||||
|
||||
case "status_cancelamento":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando status de cancelamento",))
|
||||
resultado = nfse_service.consultar_status_cancelamento_nfse(id_nfse=dados.get('id'))
|
||||
|
||||
case "resumo":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando resumo de NFSe",))
|
||||
resultado = nfse_service.consultar_resumo_nfse(id_nfse=dados.get('id'), cnpj=dados.get('cnpj'))
|
||||
|
||||
case "historico":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando histórico de NFSe",))
|
||||
resultado = nfse_service.baixar_histotico_nfse(id_nfse=dados.get('id'), cnpj=dados.get('cnpj'))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='nfse', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
raise Exception(json.dumps(resultado))
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='nfse', status='SUCESSO', mensagem='Operação concluída', json=json.dumps(resultado) if resultado else '{}', cliente=self.client, correlation_id=correlation_id)
|
||||
return self._responder("ok", resultado)
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'NFSE', tabela='nfse', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def certificado_digital(self, metodo, dados=None, correlation_id=None):
|
||||
# Gerenciamento de envio dos certificados A3/A1
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Certificado Digital - Método: {metodo.upper()}",))
|
||||
|
||||
certificado_digital = CertificadoDigital(api_key=self.api_key, banco=self.banco, url_base=self.url_base, logger=self.logger)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando certificado digital",))
|
||||
resultado = certificado_digital.post_form_data(data=dados)
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando certificados digitais",))
|
||||
resultado = certificado_digital.get(id=dados.get('id')) if dados and 'id' in dados else certificado_digital.get()
|
||||
case "put":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Atualizando certificado digital",))
|
||||
resultado = certificado_digital.put_certificado(id=dados.get('id'), data=dados)
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='certificado_digital', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
msg = resultado["error"]
|
||||
if "response" in resultado:
|
||||
msg = f"{msg} | Detalhes: {resultado['response']}"
|
||||
raise Exception(msg)
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='certificado_digital', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
return self._responder("ok", resultado)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'CERT', tabela='certificado_digital', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def empresa(self, metodo, dados=None, correlation_id=None):
|
||||
# Intermediador do cadastro e listagem das Empresas emissoras
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Empresa - Método: {metodo.upper()}",))
|
||||
|
||||
empresa_controller = EmpresaController(
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
url_base=self.url_base,
|
||||
logger=self.logger
|
||||
)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando empresa",))
|
||||
if dados and 'incentivoCultural' in dados:
|
||||
dados['incentivadorCultural'] = dados.pop('incentivoCultural')
|
||||
empresa = Empresa(**dados)
|
||||
resultado = empresa_controller.post(empresa=empresa, data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando empresas",))
|
||||
resultado = empresa_controller.get(cnpj=dados.get('cnpj'), data=dados) if dados and 'cnpj' in dados else empresa_controller.get(data=dados)
|
||||
|
||||
case "post_file":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando empresa via arquivo",))
|
||||
if not dados or 'file' not in dados or 'cnpj' not in dados:
|
||||
raise ValueError("Parâmetros 'file' e 'cnpj' são obrigatórios para 'post_file'")
|
||||
resultado = empresa_controller.post_file(file=dados['file'], cnpj=dados['cnpj'])
|
||||
|
||||
case "patch":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Atualizando certificado da empresa",))
|
||||
if not dados or 'cnpj' not in dados or 'certificado' not in dados:
|
||||
raise ValueError("Parâmetros 'cnpj' e 'certificado' (ID) são obrigatórios para 'patch'")
|
||||
resultado = empresa_controller.patch_certificado(cnpj=dados['cnpj'], certificado=dados['certificado'], data=dados)
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='empresa', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
msg = resultado["error"]
|
||||
if "response" in resultado:
|
||||
msg = f"{msg} | Detalhes: {resultado['response']}"
|
||||
raise Exception(msg)
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='empresa', status='SUCESSO', mensagem='Operação concluída', json=json.dumps(resultado) if resultado else '{}', cliente=self.client, correlation_id=correlation_id)
|
||||
return self._responder("ok", resultado)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo.upper()} - {str(e)}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'EMPRESA', tabela='empresa', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
|
||||
def servico(self, metodo, dados=None, correlation_id=None):
|
||||
# Intermediador do CRUD de Serviços com configurações de impostos
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Serviço - Método: {metodo.upper()}",))
|
||||
|
||||
servico_controller = ServicoController(
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
url_base=self.url_base,
|
||||
logger=self.logger
|
||||
)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando serviço",))
|
||||
try:
|
||||
servico = Servico(**dados)
|
||||
except ValidationError as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao validar dados do serviço: {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao='POST', tabela='servico', status='ERRO', mensagem=f'Validação: {e}', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
resultado = servico_controller.cadastrar_servico(servico=servico, data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando serviços",))
|
||||
if dados and "id" in dados:
|
||||
resultado = servico_controller.buscar_servico(id_servico=dados.get('id'), data=dados)
|
||||
else:
|
||||
resultado = servico_controller.listar_servicos(data=dados)
|
||||
|
||||
case "put":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Atualizando serviço",))
|
||||
resultado = servico_controller.atualizar_servico(id_servico=dados.get('id'), servico=dados)
|
||||
|
||||
case "delete":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Deletando serviço",))
|
||||
resultado = servico_controller.deletar_servico(id_servico=dados.get('id'))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='servico', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
msg = resultado["error"]
|
||||
if "response" in resultado:
|
||||
msg = f"{msg} | Detalhes: {resultado['response']}"
|
||||
raise Exception(msg)
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='servico', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
return self._responder("ok", resultado)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'SERVICO', tabela='servico', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def tomador(self, metodo, dados=None, correlation_id=None):
|
||||
# Gerencia clientes/clientes fiscais envolvidos nas transações (Tomadores)
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Tomador - Método: {metodo.upper()}",))
|
||||
|
||||
tomador_controller = TomadorController(
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
url_base=self.url_base,
|
||||
logger=self.logger
|
||||
)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando tomador",))
|
||||
tomador_model = Tomador(**dados)
|
||||
resultado = tomador_controller.cadastrar_tomador(tomador=tomador_model, data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando tomadores",))
|
||||
resultado = tomador_controller.buscar_tomador(cpf_cnpj=dados.get('cpf_cnpj')) if dados and 'cpf_cnpj' in dados else tomador_controller.buscar_tomador()
|
||||
|
||||
case "put":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Atualizando tomador",))
|
||||
tomador_model = Tomador(**dados)
|
||||
resultado = tomador_controller.atualizar_tomador(cpf_cnpj=dados.get('cpf_cnpj'), tomador=tomador_model)
|
||||
|
||||
case "delete":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Deletando tomador",))
|
||||
resultado = tomador_controller.deletar_tomador(cpf_cnpj=dados.get('cpf_cnpj'))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='tomador', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
msg = resultado["error"]
|
||||
if "response" in resultado:
|
||||
msg = f"{msg} | Detalhes: {resultado['response']}"
|
||||
raise Exception(msg)
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='tomador', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
return self._responder("ok", resultado)
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'TOMADOR', tabela='tomador', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def relatorios(self, metodo, dados=None, correlation_id=None):
|
||||
# Extrai planilhas mensais/períodicas e relatórios aglomerados
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Relatórios - Método: {metodo.upper()}",))
|
||||
|
||||
relatorios = Relatorios(
|
||||
url_base=self.url_base,
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
logger=self.logger
|
||||
)
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "nfe_emitidas":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Relatório de NF-e emitidas",))
|
||||
resultado = relatorios.nfe_emitidas()
|
||||
|
||||
case "nfe_por_cnpj":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Relatório de NF-e por CNPJ",))
|
||||
resultado = relatorios.nfe_por_cnpj(cnpj=dados.get('cnpj'))
|
||||
|
||||
case "nfse_emitidas":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Relatório de NFS-e emitidas",))
|
||||
resultado = relatorios.nfse_emitidas(
|
||||
data_inicial=dados.get('data_inicial'),
|
||||
data_final=dados.get('data_final')
|
||||
)
|
||||
|
||||
case "nfse_por_cnpj":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Relatório de NFS-e por CNPJ",))
|
||||
resultado = relatorios.nfse_por_cnpj(
|
||||
cnpj=dados.get('cnpj'),
|
||||
data_inicial=dados.get('data_inicial'),
|
||||
data_final=dados.get('data_final')
|
||||
)
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='relatorios', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
msg = resultado["error"]
|
||||
if "response" in resultado:
|
||||
msg = f"{msg} | Detalhes: {resultado['response']}"
|
||||
raise Exception(msg)
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='relatorios', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
return self._responder("ok", resultado)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'RELATORIOS', tabela='relatorios', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def webhooks(self, metodo, dados=None, correlation_id=None):
|
||||
# Gerencia as rotas externas para qual a PlugNotas envia atualização
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Webhooks - Método: {metodo.upper()}",))
|
||||
|
||||
webhooks = Webhooks(
|
||||
url_base=self.url_base,
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
logger=self.logger
|
||||
)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando webhooks",))
|
||||
resultado = webhooks.consultar_webhooks(cnpj=dados.get('cnpj'))
|
||||
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Criando webhook",))
|
||||
resultado = webhooks.criar_webhook(cnpj=dados.get('cnpj'), url=dados.get('url'))
|
||||
|
||||
case "delete":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Deletando webhook",))
|
||||
resultado = webhooks.deletar_webhook(cnpj=dados.get('cnpj'))
|
||||
|
||||
case "put":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Atualizando webhook",))
|
||||
resultado = webhooks.atualizar_webhook(cnpj=dados.get('cnpj'), url=dados.get('url'))
|
||||
|
||||
case "test":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Testando webhook",))
|
||||
resultado = webhooks.testar_webhook(cnpj=dados.get('cnpj'))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='webhooks', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
msg = resultado["error"]
|
||||
if "response" in resultado:
|
||||
msg = f"{msg} | Detalhes: {resultado['response']}"
|
||||
raise Exception(msg)
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='webhooks', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
return self._responder("ok", resultado)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'WEBHOOKS', tabela='webhooks', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def callback(self, message):
|
||||
#Descriptografa a msg, e repassa pra função correta (ex: nfe)
|
||||
try:
|
||||
dado = json.loads(message)
|
||||
|
||||
hmac = dado.get('hmac')
|
||||
payload = dado.get('payload')
|
||||
|
||||
if hmac and payload:
|
||||
servicekey = self.system_config.servicekey.ljust(32, '0')[:32]
|
||||
self.exchange.setCrypto(serviceid=self.system_config.serviceid,
|
||||
servicekey=servicekey)
|
||||
|
||||
if self.exchange.getPayload(hmac, payload):
|
||||
payload_dict = json.loads(self.exchange.payload)
|
||||
|
||||
# Validação do payload contra os modelos tipados
|
||||
try:
|
||||
if isinstance(payload_dict.get('dados'), dict) and 'incentivoCultural' in payload_dict['dados']:
|
||||
payload_dict['dados']['incentivadorCultural'] = payload_dict['dados'].pop('incentivoCultural')
|
||||
|
||||
validated_envelope = validate_payload(payload_dict)
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Payload validado: {payload_dict.get('fila')}/{payload_dict.get('metodo')}",))
|
||||
except (ValidationError, ValueError) as ve:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Payload inválido: {str(ve)}",))
|
||||
return json.dumps({"status": "error", "message": f"Payload inválido: {str(ve)}"})
|
||||
|
||||
config = Config(**payload_dict['config'])
|
||||
|
||||
self.client = payload_dict.get('cliente')
|
||||
self.logger.client = self.client
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Configuração recebida para cliente {self.client}",))
|
||||
|
||||
api_key_extraida = config.api_key or config.x_api_key
|
||||
expected_pg_user = os.getenv("POSTGRES_USER", "tecnospeed")
|
||||
if config.user_postgres != expected_pg_user:
|
||||
self.logger.logIC2(
|
||||
RealmType.ERRORS,
|
||||
(f"Usuário PostgreSQL inválido: {config.user_postgres}. Permitido: {expected_pg_user}",)
|
||||
)
|
||||
return json.dumps({"status": "error", "message": f"Apenas o usuário PostgreSQL '{expected_pg_user}' é permitido"})
|
||||
|
||||
if api_key_extraida and config.user_postgres and config.pass_postgres and config.host_postgres and config.port_postgres and config.database_postgres:
|
||||
|
||||
self.api_key = api_key_extraida
|
||||
self.banco = Banco(user=config.user_postgres,
|
||||
password=config.pass_postgres,
|
||||
host=config.host_postgres,
|
||||
port=config.port_postgres,
|
||||
database=config.database_postgres)
|
||||
|
||||
self.banco.create_tables()
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Configuração completa para o cliente {self.client}",))
|
||||
if 'url_base' in payload_dict:
|
||||
self.url_base = payload_dict['url_base']
|
||||
|
||||
if payload_dict['fila'] not in self.funcoes:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Fila não encontrada",))
|
||||
return json.dumps({"status":"error","message":"Fila não encontrada"})
|
||||
|
||||
corr_id = self.banco.gerar_correlation_id()
|
||||
self.logger.logIC2(RealmType.LOGS, (f"[{corr_id}] Processando {payload_dict['fila']}/{payload_dict['metodo']} para cliente {self.client}",))
|
||||
retorno = self.funcoes[payload_dict['fila']](payload_dict['metodo'], payload_dict['dados'], correlation_id=corr_id)
|
||||
return retorno
|
||||
|
||||
else:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Configuração incompleta",))
|
||||
return json.dumps({"status":"error","message":"Configuração incompleta"})
|
||||
else:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao decodificar payload - {payload}",))
|
||||
return json.dumps({"status":"error","message":"Erro ao decodificar payload"})
|
||||
|
||||
else:
|
||||
if self.logger:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao receber dado (sem hmac/payload) - {dado}",))
|
||||
return json.dumps({"status":"error","message":"Erro ao receber dado"})
|
||||
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar callback - {e}",))
|
||||
return json.dumps({"status":"error","message":str(e)})
|
||||
finally:
|
||||
if hasattr(self, 'banco') and self.banco:
|
||||
self.banco.close()
|
||||
|
||||
def run(self):
|
||||
# Ponto de Partida. Conecta no Redis, constrói os parâmetros AMQP (URL) e inicia o loop RabbitMQ
|
||||
try:
|
||||
print("[STATUS] Conectando ao RabbitMQ")
|
||||
|
||||
try:
|
||||
system_config = self.redis.get("system_config")
|
||||
if system_config:
|
||||
system_config = json.loads(system_config)
|
||||
self.system_config = SystemConfig(**system_config)
|
||||
print(f"[INFO] Configuração do sistema carregada - {self.system_config}")
|
||||
else:
|
||||
msg = "Configuração do sistema não encontrada no Redis"
|
||||
print(f"[ERRO] {msg}")
|
||||
raise Exception(msg)
|
||||
except Exception as e:
|
||||
msg = f"Erro ao conectar ao Redis - {e}"
|
||||
print(f"[ERRO] {msg}")
|
||||
raise
|
||||
|
||||
# Inicializa LoggerExchange2 após obter a URL AMQP do Redis
|
||||
builder = AppIC2Builder(service_name=self.service, versionstr=self.version, origin=self.system_config.origin)
|
||||
self.logger = LoggerExchange2(
|
||||
ic2_builder=builder,
|
||||
amqps=self.system_config.amqps,
|
||||
system=self.system,
|
||||
service=self.service,
|
||||
version=self.version,
|
||||
origin=self.system_config.origin
|
||||
)
|
||||
self.logger.logIC2(RealmType.LOGS, ("LoggerExchange2 inicializado com sucesso",))
|
||||
|
||||
self.exchange = RPCExchange2(
|
||||
amqps=self.system_config.amqps,
|
||||
origin=self.system_config.origin,
|
||||
system=self.system,
|
||||
service=self.service,
|
||||
version=self.version,
|
||||
client=self.client,
|
||||
workfunction=self.callback,
|
||||
delivery_mode = 2
|
||||
)
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Consumindo da fila {self.exchange.queue}",))
|
||||
self.exchange.start_consuming()
|
||||
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro fatal ao conectar ao RabbitMQ - {e}",))
|
||||
else:
|
||||
print(f"[ERRO] Erro ao conectar ao RabbitMQ - {e}")
|
||||
time.sleep(5)
|
||||
sys.exit(1)
|
||||
|
||||
def _responder(self, status, resultado):
|
||||
# Auxiliar para formatar a resposta RPC criptografada conforme esperado pelo Exchange2
|
||||
print(f"[RPC] Preparando resposta criptografada para o cliente (status: {status})", flush=True)
|
||||
servicekey = self.system_config.servicekey.ljust(32, '0')[:32]
|
||||
return json.dumps({
|
||||
"status": status,
|
||||
"payload": resultado, # A classe RPCExchange2 vai criptografar este campo
|
||||
"serviceid": self.system_config.serviceid,
|
||||
"servicekey": servicekey
|
||||
})
|
||||
|
||||
if __name__ == "__main__":
|
||||
main = Main()
|
||||
main.run()
|
||||
660
backup/src/mainQUEUE.py
Normal file
660
backup/src/mainQUEUE.py
Normal file
|
|
@ -0,0 +1,660 @@
|
|||
# IMPORT DAS CLASSES DE TRABALHO
|
||||
from certificado_digital.certificado_digital import CertificadoDigital
|
||||
from empresas.empresa import EmpresaController
|
||||
from NFSe.nfse import NFSeService
|
||||
from NFSe.Servico.servico import ServicoController
|
||||
from NFSe.Tomador.tomador import TomadorController
|
||||
from Relatorios.relatorios import Relatorios
|
||||
from webhooks.webhooks import Webhooks
|
||||
from NFe.registroNFe import RegistroNFeService
|
||||
|
||||
# IMPORTS DE MODELS
|
||||
from Models.config import Config, SystemConfig
|
||||
from Models.empresa import Empresa
|
||||
from Models.nfse import NFSe
|
||||
from Models.servico import Servico
|
||||
from Models.nfe import NFe
|
||||
from Models.tomador import Tomador
|
||||
from Models.payload import validate_payload
|
||||
|
||||
# IMPORTS DE BIBLIOTECAS
|
||||
from pydantic import ValidationError
|
||||
import json, sys, time, random, os, uuid
|
||||
|
||||
# IMPORTS DE CLASSES AUXILIARES
|
||||
from utils.Exchange2 import QueueExchange2, LoggerExchange2, AppIC2Builder
|
||||
from utils.exchangeModels import RealmType
|
||||
from utils.banco import Banco
|
||||
|
||||
from utils.redis import RedisClient
|
||||
|
||||
# VERSÃO DO SISTEMA
|
||||
version = "1.0.0" # Versão inicial do sistema
|
||||
version = "1.0.1" # Implementando ORM
|
||||
version = "1.0.2" # Implementando RPC
|
||||
version = "1.0.3" # Implementando configuração via Redis
|
||||
version = "2.0.0" # NFe e refatoração da Main
|
||||
version = "2.0.1" # Refatoração para QueueExchange
|
||||
|
||||
|
||||
class Main:
|
||||
def __init__(self):
|
||||
self.client = "#"
|
||||
self.api_key = ""
|
||||
self.exchange = None
|
||||
self.banco = None
|
||||
self.system_config: SystemConfig = None
|
||||
self.redis = RedisClient(host=os.getenv('REDIS_HOST', 'localhost'), port=6379)
|
||||
self.service = "tecnospeed"
|
||||
self.system = "integracao"
|
||||
self.version = version
|
||||
self.logger = None
|
||||
self.url_base = os.getenv('PLUGNOTAS_URL', 'https://api.sandbox.plugnotas.com.br')
|
||||
self.funcoes = {
|
||||
"nfe": self.nfe,
|
||||
"nfse": self.nfse,
|
||||
"certificado_digital": self.certificado_digital,
|
||||
"empresa": self.empresa,
|
||||
"servico": self.servico,
|
||||
"tomador": self.tomador,
|
||||
"relatorios": self.relatorios,
|
||||
"webhooks": self.webhooks
|
||||
}
|
||||
|
||||
# Métodos de serviços orientado a eventos.
|
||||
|
||||
def nfe(self, metodo, dados=None, correlation_id=None):
|
||||
# Roteador de emissões, baixas e cancelamentos via NFe
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de NFe - Método: {metodo.upper()}",))
|
||||
|
||||
nfe_service = RegistroNFeService(api_key=self.api_key,
|
||||
url_base=self.url_base,
|
||||
banco=self.banco,
|
||||
logger=self.logger)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando NFe",))
|
||||
nfe = NFe(**dados.get('nfe', dados))
|
||||
resultado = nfe_service.cadastrar_nfe(nfe_list=[nfe], data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando resumo de NFe",))
|
||||
resultado = nfe_service.consultar_resumo(id_nota=dados.get('id'))
|
||||
|
||||
case "download":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando PDF NFe",))
|
||||
resultado = nfe_service.baixar_pdf(id_nota=dados.get('id'))
|
||||
|
||||
case "download_xml":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando XML NFe",))
|
||||
resultado = nfe_service.baixar_xml(id_nota=dados.get('id'))
|
||||
|
||||
case "cancelar":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cancelando NFe",))
|
||||
resultado = nfe_service.cancelar_nfe(id_nota=dados.get('id'), justificativa=dados.get('justificativa', 'Cancelamento padrão'))
|
||||
|
||||
case "status_cancelamento":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando status de cancelamento",))
|
||||
resultado = nfe_service.consultar_status_cancelamento(id_nota=dados.get('id'))
|
||||
|
||||
case "cce":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Enviando Carta de Correção NFe",))
|
||||
resultado = nfe_service.enviar_cce(id_nota=dados.get('id'), correcao=dados.get('correcao'))
|
||||
|
||||
case "status_cce":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando status CCe NFe",))
|
||||
resultado = nfe_service.consultar_status_cce(id_nota=dados.get('id'))
|
||||
|
||||
case "resumo":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando resumo de NFe por CNPJ de integracao",))
|
||||
resultado = nfe_service.consultar_resumo_por_cnpj_integracao(cnpj=dados.get('cnpj'), id_integracao=dados.get('id_integracao', dados.get('id')))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='nfe', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
msg_erro = resultado["error"]
|
||||
if "response" in resultado and resultado["response"]:
|
||||
msg_erro += f" - Detalhes: {json.dumps(resultado['response'])}"
|
||||
raise Exception(msg_erro)
|
||||
|
||||
self.exchange.setPayload(json.dumps(resultado))
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='nfe', status='SUCESSO', mensagem='Operação concluída', json=json.dumps(resultado) if resultado else '{}', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "ok", "data": resultado})
|
||||
except Exception as e:
|
||||
msg_completa = str(e)
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {msg_completa}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'NFE', tabela='nfe', status='ERRO', mensagem=msg_completa, cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def nfse(self, metodo, dados=None, correlation_id=None):
|
||||
# Roteador de Notas Fiscais de Roteiro/Serviço (NFSe)
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de NFSe - Método: {metodo.upper()}",))
|
||||
|
||||
nfse_service = NFSeService(api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
url_base=self.url_base,
|
||||
logger=self.logger)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando NFSe",))
|
||||
nfse = NFSe(**dados.get('nfse', dados))
|
||||
resultado = nfse_service.cadastrar_nfse(nfse=nfse, data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando NFSe",))
|
||||
resultado = nfse_service.buscar_nfse(id_nfse=dados.get('id')) if dados and 'id' in dados else nfse_service.listar_nfse()
|
||||
|
||||
case "download":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando NFSe",))
|
||||
resultado = nfse_service.baixar_nfse(id_nfse=dados.get('id'))
|
||||
|
||||
case "download_xml":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando XML NFSe",))
|
||||
resultado = nfse_service.baixar_xml_nfse(id_nfse=dados.get('id'))
|
||||
|
||||
case "cancelar":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cancelando NFSe",))
|
||||
resultado = nfse_service.solicitar_cancelamento_nfse(id_nfse=dados.get('id'))
|
||||
|
||||
case "status_cancelamento":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando status de cancelamento",))
|
||||
resultado = nfse_service.consultar_status_cancelamento_nfse(id_nfse=dados.get('id'))
|
||||
|
||||
case "resumo":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando resumo de NFSe",))
|
||||
resultado = nfse_service.consultar_resumo_nfse(id_nfse=dados.get('id'), cnpj=dados.get('cnpj'))
|
||||
|
||||
case "historico":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Baixando histórico de NFSe",))
|
||||
resultado = nfse_service.baixar_histotico_nfse(id_nfse=dados.get('id'), cnpj=dados.get('cnpj'))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='nfse', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
raise Exception(json.dumps(resultado))
|
||||
|
||||
self.exchange.setPayload(json.dumps(resultado))
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='nfse', status='SUCESSO', mensagem='Operação concluída', json=json.dumps(resultado) if resultado else '{}', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "ok", "data": resultado})
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'NFSE', tabela='nfse', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def certificado_digital(self, metodo, dados=None, correlation_id=None):
|
||||
# Gerenciamento de envio dos certificados A3/A1
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Certificado Digital - Método: {metodo.upper()}",))
|
||||
|
||||
certificado_digital = CertificadoDigital(api_key=self.api_key, banco=self.banco, url_base=self.url_base, logger=self.logger)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando certificado digital",))
|
||||
resultado = certificado_digital.post_form_data(data=dados)
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando certificados digitais",))
|
||||
resultado = certificado_digital.get(id=dados.get('id')) if dados and 'id' in dados else certificado_digital.get()
|
||||
case "put":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Atualizando certificado digital",))
|
||||
resultado = certificado_digital.put_certificado(id=dados.get('id'), data=dados)
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='certificado_digital', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
raise Exception(resultado["error"])
|
||||
|
||||
self.exchange.setPayload(json.dumps(resultado))
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='certificado_digital', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "ok", "data": resultado})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'CERT', tabela='certificado_digital', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def empresa(self, metodo, dados=None, correlation_id=None):
|
||||
# Intermediador do cadastro e listagem das Empresas emissoras
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Enterprise - Método: {metodo.upper()}",))
|
||||
|
||||
empresa_controller = EmpresaController(
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
url_base=self.url_base,
|
||||
logger=self.logger
|
||||
)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando empresa",))
|
||||
empresa = Empresa(**dados)
|
||||
resultado = empresa_controller.post(empresa=empresa, data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando empresas",))
|
||||
resultado = empresa_controller.get(cnpj=dados.get('cnpj'), data=dados) if dados and 'cnpj' in dados else empresa_controller.get(data=dados)
|
||||
|
||||
case "post_file":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando empresa via arquivo",))
|
||||
if not dados or 'file' not in dados or 'cnpj' not in dados:
|
||||
raise ValueError("Parâmetros 'file' e 'cnpj' são obrigatórios para 'post_file'")
|
||||
resultado = empresa_controller.post_file(file=dados['file'], cnpj=dados['cnpj'])
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='Enterprise', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
raise Exception(resultado["error"])
|
||||
|
||||
self.exchange.setPayload(json.dumps(resultado))
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='Enterprise', status='SUCESSO', mensagem='Operação concluída', json=json.dumps(resultado) if resultado else '{}', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "ok", "data": resultado})
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n[ERRO CRITICAL] {metodo.upper()} -> {str(e)}\n", flush=True); self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo.upper()} - {str(e)}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'ENTERPRISE', tabela='Enterprise', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
|
||||
def servico(self, metodo, dados=None, correlation_id=None):
|
||||
# Intermediador do CRUD de Serviços com configurações de impostos
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Serviço - Método: {metodo.upper()}",))
|
||||
|
||||
servico_controller = ServicoController(
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
url_base=self.url_base,
|
||||
logger=self.logger
|
||||
)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando serviço",))
|
||||
try:
|
||||
servico = Servico(**dados)
|
||||
except ValidationError as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao validar dados do serviço: {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao='POST', tabela='servico', status='ERRO', mensagem=f'Validação: {e}', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
resultado = servico_controller.cadastrar_servico(servico=servico, data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando serviços",))
|
||||
if dados and "id" in dados:
|
||||
resultado = servico_controller.buscar_servico(id_servico=dados.get('id'), data=dados)
|
||||
else:
|
||||
resultado = servico_controller.listar_servicos(data=dados)
|
||||
|
||||
case "put":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Atualizando serviço",))
|
||||
resultado = servico_controller.atualizar_servico(id_servico=dados.get('id'), servico=dados)
|
||||
|
||||
case "delete":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Deletando serviço",))
|
||||
resultado = servico_controller.deletar_servico(id_servico=dados.get('id'))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='servico', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
raise Exception(resultado["error"])
|
||||
|
||||
self.exchange.setPayload(json.dumps(resultado))
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='servico', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "ok", "data": resultado})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'SERVICO', tabela='servico', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def tomador(self, metodo, dados=None, correlation_id=None):
|
||||
# Gerencia clientes/clientes fiscais envolvidos nas transações (Tomadores)
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Tomador - Método: {metodo.upper()}",))
|
||||
|
||||
tomador_controller = TomadorController(
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
url_base=self.url_base,
|
||||
logger=self.logger
|
||||
)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Cadastrando tomador",))
|
||||
tomador_model = Tomador(**dados)
|
||||
resultado = tomador_controller.cadastrar_tomador(tomador=tomador_model, data=dados)
|
||||
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Listando tomadores",))
|
||||
resultado = tomador_controller.buscar_tomador(cpf_cnpj=dados.get('cpf_cnpj')) if dados and 'cpf_cnpj' in dados else tomador_controller.buscar_tomador()
|
||||
|
||||
case "put":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Atualizando tomador",))
|
||||
tomador_model = Tomador(**dados)
|
||||
resultado = tomador_controller.atualizar_tomador(cpf_cnpj=dados.get('cpf_cnpj'), tomador=tomador_model)
|
||||
|
||||
case "delete":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Deletando tomador",))
|
||||
resultado = tomador_controller.deletar_tomador(cpf_cnpj=dados.get('cpf_cnpj'))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='tomador', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
raise Exception(resultado["error"])
|
||||
|
||||
self.exchange.setPayload(json.dumps(resultado))
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='tomador', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "ok", "data": resultado})
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'TOMADOR', tabela='tomador', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def relatorios(self, metodo, dados=None, correlation_id=None):
|
||||
# Extrai planilhas mensais/períodicas e relatórios aglomerados
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Relatórios - Método: {metodo.upper()}",))
|
||||
|
||||
relatorios = Relatorios(
|
||||
url_base=self.url_base,
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
logger=self.logger
|
||||
)
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "nfe_emitidas":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Relatório de NF-e emitidas",))
|
||||
resultado = relatorios.nfe_emitidas()
|
||||
|
||||
case "nfe_por_cnpj":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Relatório de NF-e por CNPJ",))
|
||||
resultado = relatorios.nfe_por_cnpj(cnpj=dados.get('cnpj'))
|
||||
|
||||
case "nfse_emitidas":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Relatório de NFS-e emitidas",))
|
||||
resultado = relatorios.nfse_emitidas(data_inicial=dados.get('data_inicial'), data_final=dados.get('data_final'))
|
||||
|
||||
case "nfse_por_cnpj":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Relatório de NFS-e por CNPJ",))
|
||||
resultado = relatorios.nfse_por_cnpj(cnpj=dados.get('cnpj'), data_inicial=dados.get('data_inicial'), data_final=dados.get('data_final'))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='relatorios', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
raise Exception(resultado["error"])
|
||||
|
||||
self.exchange.setPayload(json.dumps(resultado))
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='relatorios', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "ok", "data": resultado})
|
||||
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'RELATORIOS', tabela='relatorios', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def webhooks(self, metodo, dados=None, correlation_id=None):
|
||||
# Gerencia as rotas externas para qual a PlugNotas envia atualização
|
||||
id_usr = dados.get('id_usuario', 1) if dados else 1
|
||||
try:
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Iniciando serviço de Webhooks - Método: {metodo.upper()}",))
|
||||
|
||||
webhooks = Webhooks(
|
||||
url_base=self.url_base,
|
||||
api_key=self.api_key,
|
||||
banco=self.banco,
|
||||
logger=self.logger
|
||||
)
|
||||
|
||||
metodo = metodo.lower()
|
||||
resultado = None
|
||||
|
||||
match metodo:
|
||||
case "get":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Consultando webhooks",))
|
||||
resultado = webhooks.consultar_webhooks(cnpj=dados.get('cnpj'))
|
||||
|
||||
case "post":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Criando webhook",))
|
||||
resultado = webhooks.criar_webhook(cnpj=dados.get('cnpj'), url=dados.get('url'))
|
||||
|
||||
case "delete":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Deletando webhook",))
|
||||
resultado = webhooks.deletar_webhook(cnpj=dados.get('cnpj'))
|
||||
|
||||
case "put":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Atualizando webhook",))
|
||||
resultado = webhooks.atualizar_webhook(cnpj=dados.get('cnpj'), url=dados.get('url'))
|
||||
|
||||
case "test":
|
||||
self.logger.logIC2(RealmType.LOGS, ("Testando webhook",))
|
||||
resultado = webhooks.testar_webhook(cnpj=dados.get('cnpj'))
|
||||
|
||||
case _:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Método não encontrado",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='webhooks', status='ERRO', mensagem='Método não encontrado', cliente=self.client, correlation_id=correlation_id)
|
||||
return json.dumps({"status": "error", "message": "Método não encontrado"})
|
||||
|
||||
if isinstance(resultado, dict) and "error" in resultado:
|
||||
raise Exception(resultado["error"])
|
||||
|
||||
self.exchange.setPayload(json.dumps(resultado))
|
||||
self.logger.logIC2(RealmType.LOGS, ("Operação realizada com sucesso",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper(), tabela='webhooks', status='SUCESSO', mensagem='Operação concluída', cliente=self.client, correlation_id=correlation_id)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar método {metodo} - {e}",))
|
||||
self.banco.logar_sistema(id_usuario=id_usr, acao=metodo.upper() if isinstance(metodo, str) else 'WEBHOOKS', tabela='webhooks', status='ERRO', mensagem=str(e), cliente=self.client, correlation_id=correlation_id)
|
||||
raise e
|
||||
|
||||
def callback(self, ch, method, properties, body):
|
||||
#Descriptografa a msg, e repassa pra função correta (ex: nfe)
|
||||
try:
|
||||
message = body.decode("utf-8")
|
||||
dado = json.loads(message)
|
||||
|
||||
hmac = dado.get('hmac')
|
||||
payload = dado.get('payload')
|
||||
|
||||
if hmac and payload:
|
||||
time.sleep(random.uniform(1,5))
|
||||
servicekey = self.system_config.servicekey.ljust(32, '0')[:32]
|
||||
self.exchange.setCrypto(serviceid=self.system_config.serviceid,
|
||||
servicekey=servicekey)
|
||||
|
||||
if self.exchange.getPayload(hmac, payload):
|
||||
payload_dict = json.loads(self.exchange.payload)
|
||||
|
||||
# Validação do payload contra os modelos tipados
|
||||
try:
|
||||
if isinstance(payload_dict.get('dados'), dict) and 'incentivoCultural' in payload_dict['dados']:
|
||||
payload_dict['dados']['incentivadorCultural'] = payload_dict['dados'].pop('incentivoCultural')
|
||||
|
||||
validated_envelope = validate_payload(payload_dict)
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Payload validado: {payload_dict.get('fila')}/{payload_dict.get('metodo')}",))
|
||||
except (ValidationError, ValueError) as ve:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Payload inválido: {str(ve)}",))
|
||||
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
|
||||
return json.dumps({"status": "error", "message": f"Payload inválido: {str(ve)}"})
|
||||
|
||||
config = Config(**payload_dict['config'])
|
||||
|
||||
self.client = payload_dict.get('cliente')
|
||||
self.logger.client = self.client
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Configuração recebida para cliente {self.client}",))
|
||||
|
||||
api_key_extraida = config.api_key or config.x_api_key
|
||||
expected_pg_user = os.getenv("POSTGRES_USER", "tecnospeed")
|
||||
if config.user_postgres != expected_pg_user:
|
||||
self.logger.logIC2(
|
||||
RealmType.ERRORS,
|
||||
(f"Usuário PostgreSQL inválido: {config.user_postgres}. Permitido: {expected_pg_user}",)
|
||||
)
|
||||
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
|
||||
return json.dumps({"status": "error", "message": f"Apenas o usuário PostgreSQL '{expected_pg_user}' é permitido"})
|
||||
|
||||
if api_key_extraida and config.user_postgres and config.pass_postgres and config.host_postgres and config.port_postgres and config.database_postgres:
|
||||
|
||||
self.api_key = api_key_extraida
|
||||
self.banco = Banco(user=config.user_postgres,
|
||||
password=config.pass_postgres,
|
||||
host=config.host_postgres,
|
||||
port=config.port_postgres,
|
||||
database=config.database_postgres)
|
||||
|
||||
self.banco.create_tables()
|
||||
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Configuração completa para o cliente {self.client}",))
|
||||
if 'url_base' in payload_dict:
|
||||
self.url_base = payload_dict['url_base']
|
||||
|
||||
if payload_dict['fila'] not in self.funcoes:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Fila não encontrada",))
|
||||
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
|
||||
return json.dumps({"status":"error","message":"Fila não encontrada"})
|
||||
|
||||
corr_id = self.banco.gerar_correlation_id()
|
||||
self.logger.logIC2(RealmType.LOGS, (f"[{corr_id}] Processando {payload_dict['fila']}/{payload_dict['metodo']} para cliente {self.client}",))
|
||||
retorno = self.funcoes[payload_dict['fila']](payload_dict['metodo'], payload_dict['dados'], correlation_id=corr_id)
|
||||
ch.basic_ack(delivery_tag=method.delivery_tag)
|
||||
return retorno
|
||||
|
||||
else:
|
||||
self.logger.logIC2(RealmType.ERRORS, ("Configuração incompleta",))
|
||||
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
|
||||
return json.dumps({"status":"error","message":"Configuração incompleta"})
|
||||
else:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao decodificar payload - {payload}",))
|
||||
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
|
||||
return json.dumps({"status":"error","message":"Erro ao decodificar payload"})
|
||||
|
||||
else:
|
||||
if self.logger:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao receber dado (sem hmac/payload) - {dado}",))
|
||||
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
|
||||
return json.dumps({"status":"error","message":"Erro ao receber dado"})
|
||||
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro ao executar callback - {e}",))
|
||||
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
|
||||
return json.dumps({"status":"error","message":str(e)})
|
||||
finally:
|
||||
if hasattr(self, 'banco') and self.banco:
|
||||
self.banco.close()
|
||||
|
||||
def run(self):
|
||||
# Ponto de Partida. Conecta no Redis, constrói os parâmetros AMQP (URL) e inicia o loop RabbitMQ
|
||||
try:
|
||||
print("[STATUS] Conectando ao RabbitMQ")
|
||||
|
||||
try:
|
||||
system_config = self.redis.get("system_config")
|
||||
if system_config:
|
||||
system_config = json.loads(system_config)
|
||||
self.system_config = SystemConfig(**system_config)
|
||||
print(f"[INFO] Configuração do sistema carregada - {self.system_config}")
|
||||
else:
|
||||
msg = "Configuração do sistema não encontrada no Redis"
|
||||
print(f"[ERRO] {msg}")
|
||||
raise Exception(msg)
|
||||
except Exception as e:
|
||||
msg = f"Erro ao conectar ao Redis - {e}"
|
||||
print(f"[ERRO] {msg}")
|
||||
raise
|
||||
|
||||
# Inicializa LoggerExchange2 após obter a URL AMQP do Redis
|
||||
builder = AppIC2Builder(service_name=self.service, versionstr=self.version, origin=self.system_config.origin)
|
||||
self.logger = LoggerExchange2(
|
||||
ic2_builder=builder,
|
||||
amqps=self.system_config.amqps,
|
||||
system=self.system,
|
||||
service=self.service,
|
||||
version=self.version,
|
||||
origin=self.system_config.origin
|
||||
)
|
||||
self.logger.logIC2(RealmType.LOGS, ("LoggerExchange2 inicializado com sucesso",))
|
||||
|
||||
self.exchange = QueueExchange2(
|
||||
amqps=self.system_config.amqps,
|
||||
origin=self.system_config.origin,
|
||||
system=self.system,
|
||||
client=self.client,
|
||||
service=self.service,
|
||||
version=self.version,
|
||||
callback=self.callback,
|
||||
delivery_mode = 1)
|
||||
self.logger.logIC2(RealmType.LOGS, (f"Consumindo da fila {self.exchange.queue}",))
|
||||
self.exchange.start_consuming()
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERRO FATAL (CONSOLE)] Erro ao conectar ao RabbitMQ - {e}")
|
||||
if self.logger:
|
||||
self.logger.logIC2(RealmType.ERRORS, (f"Erro fatal ao conectar ao RabbitMQ - {e}",))
|
||||
else:
|
||||
print(f"[ERRO] Erro ao conectar ao RabbitMQ - {e}")
|
||||
time.sleep(5)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main = Main()
|
||||
main.run()
|
||||
21
backup/src/main_listener.py
Normal file
21
backup/src/main_listener.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import sys
|
||||
import os
|
||||
import uvicorn
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from listenerEndpoint import app
|
||||
|
||||
# O EndpointListener tem a função de manter a aplicação servindo de webhooks ativa recebendo HTTP e convertendo para RabbitMQ
|
||||
class EndpointListener:
|
||||
def __init__(self):
|
||||
self.host = "0.0.0.0"
|
||||
self.port = int(os.getenv("WEBHOOK_PORT", 8000))
|
||||
|
||||
def run(self):
|
||||
print(f"[STATUS] Endpoint Listener rodando na porta {self.port}...")
|
||||
uvicorn.run(app, host=self.host, port=self.port)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main = EndpointListener()
|
||||
main.run()
|
||||
1
backup/src/tests/__init__.py
Normal file
1
backup/src/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# tests package
|
||||
119
backup/src/tests/conftest.py
Normal file
119
backup/src/tests/conftest.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
"""
|
||||
Fixtures compartilhados para os testes E2E do servico_plugnotas.
|
||||
|
||||
Cria um banco SQLite in-memory, mocks do Logger e da Api,
|
||||
e uma instância pré-configurada da classe Main para cada teste.
|
||||
"""
|
||||
import pytest
|
||||
import json
|
||||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
mock_pika = MagicMock()
|
||||
sys.modules["pika"] = mock_pika
|
||||
|
||||
mock_redis_module = MagicMock()
|
||||
sys.modules["redis"] = mock_redis_module
|
||||
|
||||
from utils.banco import Base, Banco, LogTable
|
||||
from utils.api import Api
|
||||
|
||||
@pytest.fixture
|
||||
def banco_teste():
|
||||
"""Cria um Banco real com SQLite in-memory para cada teste (isolado)."""
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
Base.metadata.create_all(engine)
|
||||
Session = sessionmaker(bind=engine)
|
||||
|
||||
banco = Banco.__new__(Banco)
|
||||
banco.engine = engine
|
||||
banco.session = Session()
|
||||
|
||||
yield banco
|
||||
|
||||
banco.session.close()
|
||||
engine.dispose()
|
||||
|
||||
@pytest.fixture
|
||||
def mock_logger():
|
||||
"""Logger que grava todas as chamadas de log em uma lista para inspeção."""
|
||||
logger = MagicMock()
|
||||
logger.logs = []
|
||||
logger.client = "TestClient"
|
||||
|
||||
def capture_log(realm, msg_tuple):
|
||||
logger.logs.append({"realm": str(realm), "message": msg_tuple[0] if msg_tuple else ""})
|
||||
|
||||
logger.logIC2.side_effect = capture_log
|
||||
return logger
|
||||
|
||||
@pytest.fixture
|
||||
def mock_api():
|
||||
"""Api que retorna respostas configuráveis sem fazer chamadas HTTP reais."""
|
||||
api = MagicMock(spec=Api)
|
||||
api.get.return_value = {"status": "ok"}
|
||||
api.post.return_value = {"documents": [{"id": "PLUG-001"}]}
|
||||
api.put.return_value = {"status": "ok"}
|
||||
api.patch.return_value = {"status": "ok"}
|
||||
api.delete.return_value = {"status": "ok"}
|
||||
api.post_file.return_value = {"status": "ok"}
|
||||
api.post_form_data.return_value = {"data": {"id": "CERT-001"}}
|
||||
api.get_paginated.return_value = {"data": [], "total": 0}
|
||||
return api
|
||||
|
||||
@pytest.fixture
|
||||
def mock_exchange():
|
||||
"""Exchange que simula setPayload sem enviar nada."""
|
||||
exchange = MagicMock()
|
||||
exchange.payload = ""
|
||||
exchange.payloads_set = []
|
||||
|
||||
def capture_payload(p):
|
||||
exchange.payloads_set.append(p)
|
||||
exchange.payload = p
|
||||
|
||||
exchange.setPayload.side_effect = capture_payload
|
||||
return exchange
|
||||
|
||||
@pytest.fixture
|
||||
def main_instance(banco_teste, mock_logger, mock_exchange):
|
||||
with patch("utils.Exchange2.RPCExchange2"), \
|
||||
patch("utils.Exchange2.QueueExchange2"), \
|
||||
patch("utils.Exchange2.LoggerExchange2"), \
|
||||
patch("utils.redis.RedisClient"):
|
||||
|
||||
from main import Main
|
||||
|
||||
with patch.object(Main, '__init__', lambda self: None):
|
||||
main = Main()
|
||||
|
||||
main.client = "TestClient"
|
||||
main.api_key = "test-api-key-fake"
|
||||
main.exchange = mock_exchange
|
||||
main.banco = banco_teste
|
||||
main.system_config = MagicMock()
|
||||
main.system_config.serviceid = "test-id"
|
||||
main.system_config.servicekey = "test-key-32-chars-long-12345678"
|
||||
main.redis = MagicMock()
|
||||
main.service = "tecnospeed"
|
||||
main.system = "integracao"
|
||||
main.version = "2.0.1-test"
|
||||
main.logger = mock_logger
|
||||
main.url_base = "https://api.sandbox.plugnotas.com.br"
|
||||
|
||||
main.funcoes = {
|
||||
"nfe": main.nfe,
|
||||
"nfse": main.nfse,
|
||||
"certificado_digital": main.certificado_digital,
|
||||
"empresa": main.empresa,
|
||||
"servico": main.servico,
|
||||
"tomador": main.tomador,
|
||||
"relatorios": main.relatorios,
|
||||
"webhooks": main.webhooks,
|
||||
}
|
||||
|
||||
return main
|
||||
|
||||
67
backup/src/tests/debug_payload.py
Normal file
67
backup/src/tests/debug_payload.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Garante que o Python encontre os módulos do projeto
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from Models.payload import validate_payload
|
||||
|
||||
sample_payload = {
|
||||
"fila": "nfse",
|
||||
"cliente": "MassiveNFSeTest",
|
||||
"metodo": "POST",
|
||||
"dados": {
|
||||
"id_empresa": "some-uuid",
|
||||
"id_usuario": 1,
|
||||
"idIntegracao": "TEST_123",
|
||||
"prestador": {
|
||||
"cpfCnpj": "00000000000000"
|
||||
},
|
||||
"tomador": {
|
||||
"cpfCnpj": "08114280956",
|
||||
"razaoSocial": "NFSE 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",
|
||||
"tipoLogradouro": "Avenida"
|
||||
}
|
||||
},
|
||||
"servico": {
|
||||
"codigo": "1.01",
|
||||
"codigoTributacao": "1.01",
|
||||
"discriminacao": "SERVICO DE TESTE DE CARGA MASSIVA",
|
||||
"cnae": "6201501",
|
||||
"iss": {
|
||||
"tipoTributacao": 1,
|
||||
"exigibilidade": 1,
|
||||
"aliquota": 2.0
|
||||
},
|
||||
"valor": {
|
||||
"servico": 100.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"x_api_key": "2da392a6-79d2-4304-a8b7-959572c7e44d",
|
||||
"user_postgres": "tecnospeed",
|
||||
"pass_postgres": "u7QimG7E!SF//JC2c8",
|
||||
"host_postgres": "banco",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
},
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br"
|
||||
}
|
||||
|
||||
try:
|
||||
print("Tentando validar payload...")
|
||||
validated = validate_payload(sample_payload)
|
||||
print("Sucesso! Payload válido.")
|
||||
except Exception as e:
|
||||
print(f"Erro de validação: {e}")
|
||||
33
backup/src/tests/debug_report_api.py
Normal file
33
backup/src/tests/debug_report_api.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import requests
|
||||
import os
|
||||
import json
|
||||
|
||||
def debug_report():
|
||||
api_key = "2da392a6-79d2-4304-a8b7-959572c7e44d" # From src/utils/api.py comment
|
||||
url = "https://api.sandbox.plugnotas.com.br/nfse/relatorio?from=2026-04-01&to=2026-04-30"
|
||||
|
||||
headers = {
|
||||
"x-api-key": api_key
|
||||
}
|
||||
|
||||
print(f"Testing URL: {url}")
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
print(f"Status Code: {response.status_code}")
|
||||
try:
|
||||
print(f"Response Body: {json.dumps(response.json(), indent=2)}")
|
||||
except:
|
||||
print(f"Response Body: {response.text}")
|
||||
|
||||
# Test with ISO format
|
||||
url_iso = "https://api.sandbox.plugnotas.com.br/nfse/relatorio?from=2026-04-01T00:00:00&to=2026-04-30T23:59:59"
|
||||
print(f"\nTesting URL (ISO): {url_iso}")
|
||||
response = requests.get(url_iso, headers=headers)
|
||||
print(f"Status Code: {response.status_code}")
|
||||
try:
|
||||
print(f"Response Body: {json.dumps(response.json(), indent=2)}")
|
||||
except:
|
||||
print(f"Response Body: {response.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_report()
|
||||
46
backup/src/tests/purge_queue.py
Normal file
46
backup/src/tests/purge_queue.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import pika
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
||||
# Garante que o Python encontre os módulos do projeto
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from utils.redis import RedisClient
|
||||
|
||||
def purge_queue():
|
||||
# 1. Obter URL do RabbitMQ do Redis
|
||||
redis_host = os.getenv('REDIS_HOST', 'localhost')
|
||||
redis_client = RedisClient(host=redis_host, port=6379)
|
||||
try:
|
||||
system_config_data = redis_client.get("system_config")
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis.")
|
||||
return
|
||||
system_config = json.loads(system_config_data)
|
||||
amqps_url = system_config.get("amqps")
|
||||
except Exception as e:
|
||||
print(f"Erro ao ler configs do Redis: {e}")
|
||||
return
|
||||
|
||||
if not amqps_url:
|
||||
print("Erro: AMQPS URL não encontrada na configuração.")
|
||||
return
|
||||
|
||||
# 2. Conectar e limpar a fila
|
||||
queue_name = "integracao_tecnospeed"
|
||||
try:
|
||||
params = pika.URLParameters(amqps_url)
|
||||
connection = pika.BlockingConnection(params)
|
||||
channel = connection.channel()
|
||||
|
||||
# Tenta realizar o purge
|
||||
result = channel.queue_purge(queue=queue_name)
|
||||
print(f"Sucesso! Fila [{queue_name}] limpa. Mensagens removidas: {result.method.message_count}")
|
||||
|
||||
connection.close()
|
||||
except Exception as e:
|
||||
print(f"Erro ao limpar a fila: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
purge_queue()
|
||||
134
backup/src/tests/test.py
Normal file
134
backup/src/tests/test.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import RPCExchange2
|
||||
|
||||
def main():
|
||||
print("Iniciando script de testes da mensageria...")
|
||||
|
||||
# Carregando configuração do Redis
|
||||
redis_client = RedisClient(host=os.getenv('REDIS_HOST', 'localhost'), port=6379)
|
||||
try:
|
||||
system_config_data = redis_client.get("system_config")
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis. Certifique-se de que a variável existe.")
|
||||
return
|
||||
|
||||
system_config = SystemConfig(**json.loads(system_config_data))
|
||||
print(f"[{system_config.origin}] Configuração do sistema lida com sucesso.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erro ao ler configs do Redis: {e}")
|
||||
return
|
||||
|
||||
# Inicializando mensageria RPC
|
||||
try:
|
||||
amqps_local = system_config.amqps.replace("host.docker.internal", "localhost")
|
||||
exchange = RPCExchange2(
|
||||
amqps=amqps_local,
|
||||
origin=system_config.origin,
|
||||
system="integracao",
|
||||
service="tecnospeed",
|
||||
version="2.0.1",
|
||||
client="#"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Falha ao inicializar mensageria: {e}")
|
||||
return
|
||||
|
||||
# Configurando criptografia e payload
|
||||
servicekey = system_config.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(serviceid=system_config.serviceid, servicekey=servicekey)
|
||||
|
||||
# Payload da mensageria
|
||||
|
||||
payload_interno = {
|
||||
"fila": "nfse",
|
||||
"cliente": "Test",
|
||||
"metodo": "POST",
|
||||
"dados": {
|
||||
"idIntegracao": "INTEGRACAO-NFSE-002",
|
||||
"prestador": {
|
||||
"cpfCnpj": "08187168000160"
|
||||
},
|
||||
"tomador": {
|
||||
"cpfCnpj": "08114280956",
|
||||
"razaoSocial": "João Tomador da Silva",
|
||||
"inscricaoMunicipal": "isento",
|
||||
"email": "tomador@email.com",
|
||||
"endereco": {
|
||||
"descricaoCidade": "São Paulo",
|
||||
"cep": "00000000",
|
||||
"tipoLogradouro": "Rua",
|
||||
"logradouro": "Dois de Ouro",
|
||||
"tipoBairro": "Bairro",
|
||||
"codigoCidade": "3550308",
|
||||
"complemento": "Apto 13",
|
||||
"estado": "SP",
|
||||
"numero": "44",
|
||||
"bairro": "Centro"
|
||||
}
|
||||
},
|
||||
"servico": {
|
||||
"codigo": "1.01",
|
||||
"codigoTributacao": "1.01",
|
||||
"discriminacao": "Serviços prestados de tecnologia",
|
||||
"cnae": "6204000",
|
||||
"iss": {
|
||||
"tipoTributacao": 6,
|
||||
"exigibilidade": 1,
|
||||
"aliquota": 2.5
|
||||
},
|
||||
"valor": {
|
||||
"servico": 1500.50,
|
||||
"descontoCondicionado": 0.0,
|
||||
"descontoIncondicionado": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"x_api_key": os.getenv('PLUGNOTAS_API_KEY', ''),
|
||||
"user_postgres": os.getenv('POSTGRES_USER', 'tecnospeed'),
|
||||
"pass_postgres": os.getenv('POSTGRES_PASSWORD', 'tecnospeed'),
|
||||
"host_postgres": os.getenv('POSTGRES_HOST', 'localhost'),
|
||||
"port_postgres": os.getenv('POSTGRES_PORT', '5432'),
|
||||
"database_postgres": os.getenv('POSTGRES_DB', 'tecnospeed')
|
||||
},
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br"
|
||||
}
|
||||
|
||||
|
||||
# Criptografando e enviando tarefa
|
||||
exchange.setPayload(json.dumps(payload_interno))
|
||||
|
||||
print("Disparando chamada RPC para o RabbitMQ...")
|
||||
# Executando chamada RPC
|
||||
response = exchange.callRPC("Disparo de testes executado pelo test.py")
|
||||
print("\n[RAW RPC RESPONSE]:")
|
||||
print(response)
|
||||
|
||||
# Descriptografando a resposta
|
||||
try:
|
||||
resp_obj = json.loads(response)
|
||||
hmac_resp = resp_obj.get('hmac')
|
||||
payload_resp = resp_obj.get('payload')
|
||||
|
||||
if hmac_resp and payload_resp:
|
||||
if exchange.getPayload(hmac_resp, payload_resp):
|
||||
print("\n[DECRYPTED RPC DATA]:")
|
||||
print(json.dumps(json.loads(exchange.payload), indent=2))
|
||||
else:
|
||||
print("\n[ERROR]: Failed to decrypt response.")
|
||||
else:
|
||||
print("\n[INFO]: Response does not contain an encrypted payload.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n[ERROR]: Error processing response: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
58
backup/src/tests/testArch.py
Normal file
58
backup/src/tests/testArch.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from fastapi.testclient import TestClient
|
||||
from src.listenerEndpoint import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
PAYLOAD_VALIDO = {
|
||||
"id": "60a7c4f1c9d2a00015...",
|
||||
"status": "CONCLUIDO",
|
||||
"documento": "nfe"
|
||||
}
|
||||
HEADERS_VALIDOS = {"x-plugnotas-signature": "assinatura_hmac_valida"}
|
||||
|
||||
class TestPlugNotasWebhookEndpoint:
|
||||
|
||||
@patch("src.listenerEndpoint.exchange")
|
||||
def test_deve_receber_payload_e_enfileirar_com_sucesso(self, mock_exchange):
|
||||
"""Critério: Caminho feliz. Valida, enfileira e responde com sucesso (200)."""
|
||||
mock_exchange.sendTask.return_value = True
|
||||
|
||||
response = client.post("/listenerPlugnotas", json=PAYLOAD_VALIDO, headers=HEADERS_VALIDOS)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"status": "ok", "message": "Webhook recebido e encaminhado para processamento"}
|
||||
|
||||
mock_exchange.setPayload.assert_called_once()
|
||||
mock_exchange.sendTask.assert_called_once_with("Webhook PlugNotas Recebido")
|
||||
|
||||
@patch("src.listenerEndpoint.exchange")
|
||||
def test_deve_rejeitar_payload_invalido_antes_de_processar(self, mock_exchange):
|
||||
"""Critério: Integridade de Dados. Valida campos obrigatórios recebidos do Hook."""
|
||||
payload_invalido = {"status": "CONCLUIDO"} # Faltando ID e documento
|
||||
|
||||
response = client.post("/listenerPlugnotas", json=payload_invalido, headers=HEADERS_VALIDOS)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "warning"
|
||||
assert "Campos faltantes" in response.json()["message"]
|
||||
|
||||
@patch("src.listenerEndpoint.exchange")
|
||||
def test_deve_retornar_erro_se_internal_error_no_rabbitmq(self, mock_exchange):
|
||||
"""Critério: Resiliência. Retorna 500 em caso de erro ao enviar para fila."""
|
||||
# Simulando uma queda ao enviar task
|
||||
mock_exchange.sendTask.side_effect = Exception("RabbitMQ is down")
|
||||
|
||||
response = client.post("/listenerPlugnotas", json=PAYLOAD_VALIDO, headers=HEADERS_VALIDOS)
|
||||
|
||||
assert response.status_code == 500
|
||||
assert response.json()["message"] == "Erro interno no processamento do webhook"
|
||||
|
||||
def test_deve_retornar_erro_se_exchange_nao_inicializado(self):
|
||||
"""Critério: Resiliência. Retorna 500 se o exchange global for nulo."""
|
||||
# Sem patch de exchange, então pegará o exchange default (None) em client sem lifespan completo
|
||||
response = client.post("/listenerPlugnotas", json=PAYLOAD_VALIDO)
|
||||
|
||||
assert response.status_code == 500
|
||||
assert response.json()["message"] == "Serviço temporariamente indisponível"
|
||||
144
backup/src/tests/test_1000_empresas.py
Normal file
144
backup/src/tests/test_1000_empresas.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
|
||||
# Garante que o Python encontre os módulos do projeto
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import RPCExchange2, QueueExchange2
|
||||
|
||||
def get_empresa_payload(i):
|
||||
"""Gera um payload de empresa com CNPJ e Razão Social baseados no índice i"""
|
||||
# Gerando um CNPJ fictício baseado no loop para evitar duplicidade no banco
|
||||
# Ex: 94074986000001, 94074986000002...
|
||||
cnpj = f"94074986{str(i).zfill(6)}"
|
||||
|
||||
return {
|
||||
"id_usuario": 1,
|
||||
"cpfCnpj": cnpj,
|
||||
"razaoSocial": f"Varejo de Teste {i} LTDA",
|
||||
"nomeFantasia": f"Varejo {i}",
|
||||
"inscricaoMunicipal": "0001",
|
||||
"inscricaoEstadual": "123456789",
|
||||
"simplesNacional": True,
|
||||
"regimeTributario": 1,
|
||||
"regimeTributarioEspecial": 1,
|
||||
"incentivadorCultural": False,
|
||||
"incentivoFiscal": True,
|
||||
"certificado": "5af59d271f6e8f409178fbf3",
|
||||
"endereco": {
|
||||
"codigoPais": "1058",
|
||||
"descricaoPais": "BRASIL",
|
||||
"tipoLogradouro": "Avenida",
|
||||
"logradouro": "Avenida de Teste",
|
||||
"numero": str(i),
|
||||
"complemento": "Casa",
|
||||
"tipoBairro": "Bairro",
|
||||
"bairro": "Centro",
|
||||
"codigoCidade": "4115200",
|
||||
"descricaoCidade": "Maringá",
|
||||
"estado": "PR",
|
||||
"cep": "87020025"
|
||||
},
|
||||
"nfe": {"ativo": True, "tipoContrato": 0},
|
||||
"nfse": {"ativo": False}
|
||||
}
|
||||
|
||||
def run_rpc_test(exchange, config_rpc):
|
||||
"""Envia uma única empresa via RPC e aguarda a resposta (bom para debug)"""
|
||||
print("\n--- INICIANDO TESTE RPC INDIVIDUAL ---")
|
||||
payload_dados = get_empresa_payload(1)
|
||||
|
||||
full_payload = {
|
||||
"fila": "empresa",
|
||||
"metodo": "post",
|
||||
"cliente": "tray",
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br",
|
||||
"config": config_rpc,
|
||||
"dados": payload_dados
|
||||
}
|
||||
|
||||
print(f"Enviando empresa {payload_dados['cpfCnpj']} via RPC...")
|
||||
exchange.setPayload(json.dumps(full_payload))
|
||||
resposta = exchange.callRPC(json.dumps(full_payload), timeout=30)
|
||||
|
||||
print("\nRESPOSTA DO SERVIDOR:")
|
||||
print(resposta)
|
||||
print("--------------------------------------\n")
|
||||
|
||||
def run_mass_test(exchange, config_rpc, total=1000):
|
||||
"""Envia N empresas via Fila (sem esperar resposta), ideal para teste de carga"""
|
||||
print(f"\n--- INICIANDO CARGA MASSIVA: {total} Mensagens ---")
|
||||
start_time = time.time()
|
||||
|
||||
for i in range(1, total + 1):
|
||||
payload_dados = get_empresa_payload(i)
|
||||
|
||||
full_payload = {
|
||||
"fila": "empresa",
|
||||
"metodo": "post",
|
||||
"cliente": "MassTest",
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br",
|
||||
"config": config_rpc,
|
||||
"dados": payload_dados
|
||||
}
|
||||
|
||||
exchange.setPayload(json.dumps(full_payload))
|
||||
exchange.sendMsg(json.dumps(full_payload), service="tecnospeed")
|
||||
|
||||
if i % 100 == 0:
|
||||
print(f"Enviadas: {i}/{total}...")
|
||||
|
||||
end_time = time.time()
|
||||
print(f"Carga concluída em {end_time - start_time:.2f} segundos.")
|
||||
print("--------------------------------------\n")
|
||||
|
||||
def main():
|
||||
# 1. Carrega configurações do Redis
|
||||
redis_host = os.getenv('REDIS_HOST', 'localhost')
|
||||
redis_client = RedisClient(host=redis_host, port=6379)
|
||||
|
||||
try:
|
||||
config_data = redis_client.get("system_config")
|
||||
if not config_data:
|
||||
print("Erro: system_config não encontrado no Redis.")
|
||||
return
|
||||
sc = SystemConfig(**json.loads(config_data))
|
||||
except Exception as e:
|
||||
print(f"Erro ao carregar configs: {e}")
|
||||
return
|
||||
|
||||
# Configurações de Banco e API para o Worker
|
||||
config_rpc = {
|
||||
"x_api_key": "2da392a6-79d2-4304-a8b7-959572c7e44d",
|
||||
"user_postgres": "tecnospeed",
|
||||
"pass_postgres": "u7QimG7E%21SF%2F%2FJC2c8",
|
||||
"host_postgres": "banco",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
}
|
||||
|
||||
print(f"Conectando ao RabbitMQ de {sc.origin}...")
|
||||
|
||||
# 2. Escolha do Modo de Operação
|
||||
# Mude para True para testar o retorno (RPC Server) ou False para carga assíncrona (Queue Worker)
|
||||
MODO_DEBUG_RPC = True
|
||||
|
||||
if MODO_DEBUG_RPC:
|
||||
# Usa RPCExchange2 para modo Síncrono (Request-Response)
|
||||
exchange = RPCExchange2(sc.amqps, sc.origin, "integracao", "tecnospeed", "2.1.0", client="")
|
||||
servicekey = sc.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(sc.serviceid, servicekey)
|
||||
run_rpc_test(exchange, config_rpc)
|
||||
else:
|
||||
# Usa QueueExchange2 para modo Assíncrono (Fire and Forget)
|
||||
exchange = QueueExchange2(sc.amqps, sc.origin, "integracao", "tecnospeed", "2.1.0", client="#")
|
||||
servicekey = sc.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(sc.serviceid, servicekey)
|
||||
run_mass_test(exchange, config_rpc, total=1000)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
219
backup/src/tests/test_1000_nfe.py
Normal file
219
backup/src/tests/test_1000_nfe.py
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
|
||||
# Adiciona o diretório atual ao path para importar as classes locais
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import QueueExchange2
|
||||
from utils.banco import Banco, EnterpriseTable
|
||||
import random
|
||||
|
||||
def main():
|
||||
print("Iniciando script de teste de carga: 1000 NFes...", flush=True)
|
||||
|
||||
# 1. Carregando configuração do Redis para obter dados do RabbitMQ
|
||||
redis_host = os.getenv('REDIS_HOST', 'localhost')
|
||||
redis_client = RedisClient(host=redis_host, port=6379)
|
||||
try:
|
||||
system_config_data = redis_client.get("system_config")
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis.")
|
||||
return
|
||||
|
||||
system_config = SystemConfig(**json.loads(system_config_data))
|
||||
print(f"Configuração [{system_config.origin}] carregada.", flush=True)
|
||||
except Exception as e:
|
||||
print(f"Erro ao ler configs do Redis: {e}")
|
||||
return
|
||||
|
||||
# 2. Inicializando a conexão com a FILA (RabbitMQ)
|
||||
try:
|
||||
print("Tentando conectar ao RabbitMQ...", flush=True)
|
||||
exchange = QueueExchange2(
|
||||
amqps=system_config.amqps,
|
||||
origin=system_config.origin,
|
||||
system="integracao",
|
||||
service="tecnospeed",
|
||||
version="2.1.0",
|
||||
client="#"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Falha ao iniciar mensageria: {e}")
|
||||
return
|
||||
|
||||
# 3. Configurando criptografia (necessário para o worker aceitar a mensagem)
|
||||
servicekey = system_config.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(serviceid=system_config.serviceid, servicekey=servicekey)
|
||||
|
||||
# 4. Buscando empresas válidas no banco para o teste
|
||||
try:
|
||||
# Configuração do banco - Força conexão interna se estiver no Docker
|
||||
# O .env às vezes aponta para o IP externo que não resolve dentro do container
|
||||
db_host = os.getenv('POSTGRES_HOST', 'postgres')
|
||||
db_port = int(os.getenv('POSTGRES_PORT', 5432))
|
||||
|
||||
# Se estivermos DENTRO do docker, o host 'postgres' é o correto.
|
||||
if db_host != 'postgres':
|
||||
print(f"Aviso: POSTGRES_HOST no ambiente é {db_host}. Tentando 'postgres' primeiro por estar no Docker...")
|
||||
db_host = 'postgres'
|
||||
db_port = 5432
|
||||
|
||||
print(f"Conectando ao banco em {db_host}:{db_port}...")
|
||||
|
||||
banco = Banco(
|
||||
host=db_host,
|
||||
port=db_port,
|
||||
user=os.getenv('POSTGRES_USER', 'tecnospeed'),
|
||||
password=os.getenv('POSTGRES_PASSWORD', 'u7QimG7E!SF//JC2c8'),
|
||||
database=os.getenv('POSTGRES_DB', 'tecnospeed')
|
||||
)
|
||||
print("Executando query na tabela Enterprise...", flush=True)
|
||||
empresas = banco.session.query(EnterpriseTable.id, EnterpriseTable.cnpj).filter(EnterpriseTable.cnpj != None, EnterpriseTable.cnpj != "").limit(100).all()
|
||||
lista_empresas = [{"id": emp.id, "cnpj": emp.cnpj} for emp in empresas]
|
||||
if not lista_empresas:
|
||||
print("Erro: Nenhuma empresa com CNPJ válido encontrada na tabela 'Enterprise'.", flush=True)
|
||||
return
|
||||
print(f"Sucesso: {len(lista_empresas)} empresas válidas encontradas para o teste.", flush=True)
|
||||
except Exception as e:
|
||||
print(f"Erro ao conectar ao banco de dados: {e}")
|
||||
return
|
||||
|
||||
# Base do payload fornecido pelo usuário (omitindo emitente fixo para preencher no loop)
|
||||
payload_base = {
|
||||
"nfe": {
|
||||
"idIntegracao": "", # Será preenchido no loop
|
||||
"presencial": True,
|
||||
"consumidorFinal": True,
|
||||
"natureza": "OPERAÇÃO INTERNA",
|
||||
"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.6
|
||||
}
|
||||
],
|
||||
"responsavelTecnico": {
|
||||
"cpfCnpj": "08187168000160",
|
||||
"nome": "Tecnospeed",
|
||||
"email": "contato@tecnospeed.com.br",
|
||||
"telefone": {
|
||||
"ddd": "44",
|
||||
"numero": "30379500"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Estrutura completa exigida pelo mainQUEUE.py
|
||||
def get_full_payload(id_num, id_empresa, cnpj_empresa):
|
||||
# Gerando um timestamp para garantir idIntegracao único
|
||||
ts = int(time.time())
|
||||
payload = json.loads(json.dumps(payload_base)) # Deep copy simples
|
||||
payload["nfe"]["idIntegracao"] = f"TESTE_{ts}_{id_num}"
|
||||
payload["nfe"]["emitente"] = {
|
||||
"cpfCnpj": cnpj_empresa,
|
||||
"inscricaoEstadual": "ISENTO"
|
||||
}
|
||||
|
||||
return {
|
||||
"fila": "nfe",
|
||||
"cliente": "LoadTest1000",
|
||||
"metodo": "POST",
|
||||
"dados": {
|
||||
"id_empresa": id_empresa,
|
||||
**payload
|
||||
},
|
||||
"config": {
|
||||
"x_api_key": "2da392a6-79d2-4304-a8b7-959572c7e44d",
|
||||
"user_postgres": "tecnospeed",
|
||||
"pass_postgres": "u7QimG7E!SF//JC2c8",
|
||||
"host_postgres": "postgres",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
},
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br"
|
||||
}
|
||||
|
||||
print(f"Enviando 1000 mensagens para a fila...")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
for i in range(1, 1001):
|
||||
empresa_sorteada = random.choice(lista_empresas)
|
||||
full_payload = get_full_payload(i, id_empresa=empresa_sorteada["id"], cnpj_empresa=empresa_sorteada["cnpj"])
|
||||
# É necessário criptografar o payload antes de cada envio
|
||||
exchange.setPayload(json.dumps(full_payload))
|
||||
exchange.sendMsg(json.dumps(full_payload), service="tecnospeed")
|
||||
|
||||
if i % 100 == 0:
|
||||
print(f"Enviadas: {i}/1000...", flush=True)
|
||||
|
||||
end_time = time.time()
|
||||
|
||||
print("\nConcluído!")
|
||||
print(f"Tempo total de envio: {end_time - start_time:.2f} segundos.")
|
||||
print("Agora você pode acompanhar o processamento nos logs do worker (tecnospeed-queue).")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
173
backup/src/tests/test_1000_nfse.py
Normal file
173
backup/src/tests/test_1000_nfse.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
|
||||
# Adiciona o diretório atual ao path para importar as classes locais
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Adiciona o diretório src ao path para encontrar os Models e Utils
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import QueueExchange2
|
||||
from utils.banco import Banco, EnterpriseTable
|
||||
|
||||
def main():
|
||||
print("Iniciando script de teste de carga: 1000 NFSe...", flush=True)
|
||||
|
||||
# 1. Carregando configuração do Redis para obter dados do RabbitMQ
|
||||
redis_host = os.getenv('REDIS_HOST', 'localhost')
|
||||
redis_client = RedisClient(host=redis_host, port=6379)
|
||||
try:
|
||||
system_config_data = redis_client.get("system_config")
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis.")
|
||||
return
|
||||
|
||||
system_config = SystemConfig(**json.loads(system_config_data))
|
||||
print(f"Configuração [{system_config.origin}] carregada.", flush=True)
|
||||
except Exception as e:
|
||||
print(f"Erro ao ler configs do Redis: {e}")
|
||||
return
|
||||
|
||||
# 2. Inicializando a conexão com a FILA (RabbitMQ)
|
||||
try:
|
||||
print("Tentando conectar ao RabbitMQ...", flush=True)
|
||||
exchange = QueueExchange2(
|
||||
amqps=system_config.amqps,
|
||||
origin=system_config.origin,
|
||||
system="integracao",
|
||||
service="tecnospeed",
|
||||
version="2.1.0",
|
||||
client="#"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Falha ao iniciar mensageria: {e}")
|
||||
return
|
||||
|
||||
# 3. Configurando criptografia (necessário para o worker aceitar a mensagem)
|
||||
servicekey = system_config.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(serviceid=system_config.serviceid, servicekey=servicekey)
|
||||
|
||||
# 4. Buscando empresas válidas no banco para o teste
|
||||
try:
|
||||
db_host = os.getenv('POSTGRES_HOST', 'banco')
|
||||
db_port = int(os.getenv('POSTGRES_PORT', 5432))
|
||||
|
||||
# Se estivermos DENTRO do docker e o host configurado for o IP externo ou porta mapeada, forçamos 'banco' e '5432'
|
||||
if os.path.exists('/.dockerenv') or os.path.exists('/proc/1/cgroup'):
|
||||
print(f"Aviso: Ambiente Docker detectado. Forçando host 'banco' e porta 5432.")
|
||||
db_host = 'banco'
|
||||
db_port = 5432
|
||||
|
||||
print(f"Conectando ao banco em {db_host}:{db_port}...")
|
||||
|
||||
banco = Banco(
|
||||
host=db_host,
|
||||
port=db_port,
|
||||
user=os.getenv('POSTGRES_USER', 'tecnospeed'),
|
||||
password=os.getenv('POSTGRES_PASSWORD', 'u7QimG7E!SF//JC2c8'),
|
||||
database=os.getenv('POSTGRES_DB', 'tecnospeed')
|
||||
)
|
||||
print("Executando query na tabela Enterprise...", flush=True)
|
||||
empresas = banco.session.query(EnterpriseTable.id, EnterpriseTable.cnpj).filter(EnterpriseTable.cnpj != None, EnterpriseTable.cnpj != "").limit(100).all()
|
||||
lista_empresas = [{"id": emp.id, "cnpj": emp.cnpj} for emp in empresas]
|
||||
if not lista_empresas:
|
||||
print("Erro: Nenhuma empresa com CNPJ válido encontrada na tabela 'Enterprise'.", flush=True)
|
||||
return
|
||||
print(f"Sucesso: {len(lista_empresas)} empresas válidas encontradas para o teste.", flush=True)
|
||||
except Exception as e:
|
||||
print(f"Erro ao conectar ao banco de dados: {e}")
|
||||
return
|
||||
|
||||
# Base do payload de NFSe
|
||||
payload_base = {
|
||||
"nfse": {
|
||||
"idIntegracao": "", # Será preenchido no loop
|
||||
"prestador": {
|
||||
"cpfCnpj": "" # Será preenchido no loop
|
||||
},
|
||||
"tomador": {
|
||||
"cpfCnpj": "08114280956",
|
||||
"razaoSocial": "NFSE 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",
|
||||
"tipoLogradouro": "Avenida"
|
||||
}
|
||||
},
|
||||
"servico": {
|
||||
"codigo": "1.01",
|
||||
"codigoTributacao": "1.01",
|
||||
"discriminacao": "SERVICO DE TESTE DE CARGA MASSIVA",
|
||||
"cnae": "6201501",
|
||||
"iss": {
|
||||
"tipoTributacao": 1,
|
||||
"exigibilidade": 1,
|
||||
"aliquota": 2.0
|
||||
},
|
||||
"valor": {
|
||||
"servico": 100.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Estrutura completa exigida pelo mainQUEUE.py
|
||||
def get_full_payload(id_num, id_empresa, cnpj_empresa):
|
||||
ts = int(time.time() * 1000)
|
||||
payload = json.loads(json.dumps(payload_base)) # Deep copy
|
||||
payload["nfse"]["idIntegracao"] = f"LOAD_NFSE_{ts}_{id_num}"
|
||||
payload["nfse"]["prestador"]["cpfCnpj"] = cnpj_empresa
|
||||
|
||||
return {
|
||||
"fila": "nfse",
|
||||
"cliente": "MassiveNFSeTest",
|
||||
"metodo": "POST",
|
||||
"dados": {
|
||||
"id_empresa": id_empresa,
|
||||
"id_usuario": 1,
|
||||
**payload["nfse"]
|
||||
},
|
||||
"config": {
|
||||
"x_api_key": "2da392a6-79d2-4304-a8b7-959572c7e44d",
|
||||
"user_postgres": "tecnospeed",
|
||||
"pass_postgres": "u7QimG7E!SF//JC2c8",
|
||||
"host_postgres": "banco",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
},
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br"
|
||||
}
|
||||
|
||||
print(f"Enviando 1000 NFSe para a fila...")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
for i in range(1, 1001):
|
||||
empresa_sorteada = random.choice(lista_empresas)
|
||||
full_payload = get_full_payload(i, id_empresa=empresa_sorteada["id"], cnpj_empresa=empresa_sorteada["cnpj"])
|
||||
|
||||
exchange.setPayload(json.dumps(full_payload))
|
||||
exchange.sendMsg(json.dumps(full_payload), service="tecnospeed")
|
||||
|
||||
if i % 100 == 0:
|
||||
print(f"Enviadas: {i}/1000...", flush=True)
|
||||
|
||||
end_time = time.time()
|
||||
|
||||
print("\nConcluído!")
|
||||
print(f"Tempo total de envio: {end_time - start_time:.2f} segundos.")
|
||||
print("Acompanhe o processamento nos logs do worker (tecnospeed-queue).")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
backup/src/tests/test_api.py
Normal file
29
backup/src/tests/test_api.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
from utils.api import Api
|
||||
|
||||
api = Api("2da392a6-79d2-4304-a8b7-959572c7e44d")
|
||||
|
||||
print("\n--- Consultando Certificados ---")
|
||||
# GET /certificado (Lista todos)
|
||||
certificados = api.get("https://api.sandbox.plugnotas.com.br/certificado")
|
||||
print("GET /certificado response:")
|
||||
print(json.dumps(certificados, indent=2))
|
||||
|
||||
# Se houver certificados, faz um GET específico no primeiro
|
||||
if isinstance(certificados, list) and len(certificados) > 0:
|
||||
first_cert_id = certificados[0].get('id')
|
||||
print(f"\nConsultando certificado específico (ID: {first_cert_id}):")
|
||||
spec_cert = api.get(f"https://api.sandbox.plugnotas.com.br/certificado/{first_cert_id}")
|
||||
print(json.dumps(spec_cert, indent=2))
|
||||
elif isinstance(certificados, dict) and "data" in certificados:
|
||||
# Caso a resposta venha com envelope "data"
|
||||
data_list = certificados["data"]
|
||||
if data_list:
|
||||
first_cert_id = data_list[0].get('id')
|
||||
print(f"\nConsultando certificado específico (ID: {first_cert_id}):")
|
||||
spec_cert = api.get(f"https://api.sandbox.plugnotas.com.br/certificado/{first_cert_id}")
|
||||
print(json.dumps(spec_cert, indent=2))
|
||||
96
backup/src/tests/test_callback.py
Normal file
96
backup/src/tests/test_callback.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import json
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
class TestRoteamento:
|
||||
"""Verifica que todas as filas registradas no dict existem e são callable."""
|
||||
|
||||
def test_todas_filas_registradas(self, main_instance):
|
||||
filas_esperadas = ["nfe", "nfse", "certificado_digital", "empresa",
|
||||
"servico", "tomador", "relatorios", "webhooks"]
|
||||
for fila in filas_esperadas:
|
||||
assert fila in main_instance.funcoes, f"Fila '{fila}' não está registrada"
|
||||
assert callable(main_instance.funcoes[fila]), f"Fila '{fila}' não é callable"
|
||||
|
||||
def test_fila_inexistente_nao_registrada(self, main_instance):
|
||||
assert "webhook_callback" not in main_instance.funcoes
|
||||
assert "fila_fantasma" not in main_instance.funcoes
|
||||
|
||||
|
||||
class TestCallbackErros:
|
||||
"""Testa que o callback lida corretamente com payloads inválidos."""
|
||||
|
||||
def test_callback_json_invalido(self, main_instance):
|
||||
"""Payload que não é JSON válido deve retornar erro."""
|
||||
resultado = json.loads(main_instance.callback("isso não é json"))
|
||||
assert resultado["status"] == "error"
|
||||
|
||||
def test_callback_sem_hmac(self, main_instance):
|
||||
"""Payload sem HMAC deve retornar erro."""
|
||||
resultado = json.loads(main_instance.callback(json.dumps({"dados": "algo"})))
|
||||
assert resultado["status"] == "error"
|
||||
|
||||
def test_callback_com_hmac_e_payload(self, main_instance, mock_exchange):
|
||||
"""Payload com hmac+payload mas sem crypto configurada deve retornar erro."""
|
||||
mock_exchange.getPayload.return_value = False
|
||||
|
||||
main_instance.system_config = MagicMock()
|
||||
main_instance.system_config.servicekey = "testkey1234567890"
|
||||
main_instance.system_config.serviceid = "test-service"
|
||||
|
||||
resultado = json.loads(main_instance.callback(json.dumps({
|
||||
"hmac": "fake-hmac",
|
||||
"payload": "fake-encrypted-payload"
|
||||
})))
|
||||
|
||||
assert resultado["status"] == "error"
|
||||
|
||||
|
||||
class TestBancoIntegracao:
|
||||
"""Testa que os logs são gravados no banco durante as operações."""
|
||||
|
||||
def test_log_gravado_no_banco(self, main_instance, banco_teste, mock_api):
|
||||
"""Ao executar uma operação, deve gravar log no banco."""
|
||||
mock_api.get.return_value = {"id": "PLUG-NFE-001", "status": "CONCLUIDO"}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
main_instance.nfe("get", {"id": "PLUG-NFE-001", "id_usuario": 42})
|
||||
|
||||
from utils.banco import LogTable
|
||||
logs = banco_teste.session.query(LogTable).all()
|
||||
|
||||
# Deve ter pelo menos 2 logs: INICIO + SUCESSO
|
||||
assert len(logs) >= 2
|
||||
acoes = [log.acao for log in logs]
|
||||
assert "GET" in acoes
|
||||
|
||||
# Verifica que o id_usuario foi gravado
|
||||
assert any(log.id_usuario == 42 for log in logs)
|
||||
|
||||
def test_log_erro_gravado(self, main_instance, banco_teste, mock_api):
|
||||
"""Erros também devem ser gravados no banco."""
|
||||
mock_api.get.side_effect = Exception("Timeout na API")
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
with pytest.raises(Exception):
|
||||
main_instance.nfe("get", {"id": "PLUG-NFE-001", "id_usuario": 7})
|
||||
|
||||
from utils.banco import LogTable
|
||||
logs = banco_teste.session.query(LogTable).filter_by(status="ERRO").all()
|
||||
assert len(logs) >= 1
|
||||
|
||||
|
||||
class TestExchangePayload:
|
||||
"""Testa que o exchange.setPayload é chamado com o resultado correto."""
|
||||
|
||||
def test_payload_set_no_sucesso(self, main_instance, mock_api, mock_exchange):
|
||||
"""Ao concluir com sucesso, setPayload deve ser chamado com o resultado."""
|
||||
mock_api.get.return_value = {"id": "PLUG-NFE-001", "status": "CONCLUIDO"}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
main_instance.nfe("get", {"id": "PLUG-NFE-001", "id_usuario": 1})
|
||||
|
||||
assert mock_exchange.setPayload.called
|
||||
payload_enviado = json.loads(mock_exchange.payloads_set[-1])
|
||||
assert payload_enviado["status"] == "CONCLUIDO"
|
||||
179
backup/src/tests/test_nfe.py
Normal file
179
backup/src/tests/test_nfe.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
import json
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
class TestNFePost:
|
||||
|
||||
@pytest.fixture
|
||||
def dados_nfe(self):
|
||||
return {
|
||||
"id_usuario": 1,
|
||||
"id_empresa": 10,
|
||||
"nfe": {
|
||||
"idIntegracao": "INT-NFE-001",
|
||||
"presencial": True,
|
||||
"consumidorFinal": True,
|
||||
"natureza": "Venda de mercadoria",
|
||||
"emitente": {"cpfCnpj": "08187168000160"},
|
||||
"destinatario": {
|
||||
"cpfCnpj": "08114280956",
|
||||
"razaoSocial": "Destinatário Teste",
|
||||
"endereco": {
|
||||
"logradouro": "Rua Teste",
|
||||
"numero": "100",
|
||||
"bairro": "Centro",
|
||||
"codigoCidade": "5201405",
|
||||
"descricaoCidade": "GOIANIA",
|
||||
"estado": "GO",
|
||||
"cep": "74000000"
|
||||
}
|
||||
},
|
||||
"itens": [{
|
||||
"codigo": "001",
|
||||
"descricao": "Produto Teste",
|
||||
"ncm": "61091000",
|
||||
"cfop": "5102",
|
||||
"valorUnitario": {"comercial": 100.0, "tributavel": 100.0},
|
||||
"valor": 100.0,
|
||||
"tributos": {
|
||||
"icms": {
|
||||
"origem": "0",
|
||||
"cst": "00",
|
||||
"baseCalculo": {"modalidadeDeterminacao": 0, "valor": 100.0},
|
||||
"aliquota": 18.0,
|
||||
"valor": 18.0
|
||||
},
|
||||
"pis": {
|
||||
"cst": "01",
|
||||
"baseCalculo": {"valor": 100.0, "quantidade": 1},
|
||||
"aliquota": 1.65,
|
||||
"valor": 1.65
|
||||
},
|
||||
"cofins": {
|
||||
"cst": "01",
|
||||
"baseCalculo": {"valor": 100.0},
|
||||
"aliquota": 7.6,
|
||||
"valor": 7.6
|
||||
}
|
||||
}
|
||||
}],
|
||||
"pagamentos": [{"aVista": True, "meio": "01", "valor": 100.0}]
|
||||
}
|
||||
}
|
||||
|
||||
def test_post_nfe_sucesso(self, main_instance, mock_api, dados_nfe):
|
||||
mock_api.post.return_value = {
|
||||
"documents": [{"id": "PLUG-NFE-001"}]
|
||||
}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfe("post", dados_nfe))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
assert resultado["data"]["documents"][0]["id"] == "PLUG-NFE-001"
|
||||
|
||||
def test_post_nfe_erro_api(self, main_instance, mock_api, dados_nfe):
|
||||
mock_api.post.return_value = {"error": "CPF/CNPJ inválido"}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
with pytest.raises(Exception, match="CPF/CNPJ inválido"):
|
||||
main_instance.nfe("post", dados_nfe)
|
||||
|
||||
class TestNFeGet:
|
||||
|
||||
def test_get_nfe_resumo(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {
|
||||
"id": "PLUG-NFE-001",
|
||||
"status": "CONCLUIDO",
|
||||
"xml": "https://..."
|
||||
}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfe("get", {"id": "PLUG-NFE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
assert resultado["data"]["status"] == "CONCLUIDO"
|
||||
|
||||
class TestNFeDownload:
|
||||
"""Testa download de PDF e XML de NFe."""
|
||||
|
||||
def test_download_pdf(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"url": "https://cdn.plugnotas.com.br/nfe/pdf/123"}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfe("download", {"id": "PLUG-NFE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_download_xml(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"url": "https://cdn.plugnotas.com.br/nfe/xml/123"}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfe("download_xml", {"id": "PLUG-NFE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
class TestNFeCancelar:
|
||||
|
||||
def test_cancelar_nfe_sucesso(self, main_instance, mock_api):
|
||||
mock_api.post.return_value = {"id": "PLUG-NFE-001", "status": "CANCELAMENTO_SOLICITADO"}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfe("cancelar", {
|
||||
"id": "PLUG-NFE-001",
|
||||
"justificativa": "Erro de emissão",
|
||||
"id_usuario": 1
|
||||
}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_status_cancelamento(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"id": "PLUG-NFE-001", "status": "CANCELADO"}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfe("status_cancelamento", {"id": "PLUG-NFE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
class TestNFeCCe:
|
||||
|
||||
def test_enviar_cce(self, main_instance, mock_api):
|
||||
mock_api.post.return_value = {"id": "PLUG-NFE-001", "status": "CCE_ENVIADA"}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfe("cce", {
|
||||
"id": "PLUG-NFE-001",
|
||||
"correcao": "Corrigir endereço do destinatário",
|
||||
"id_usuario": 1
|
||||
}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_status_cce(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"id": "PLUG-NFE-001", "status": "CCE_AUTORIZADA"}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfe("status_cce", {"id": "PLUG-NFE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
class TestNFeResumo:
|
||||
|
||||
def test_resumo_por_cnpj(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"id": "PLUG-NFE-001", "chave": "3524..."}
|
||||
|
||||
with patch("NFe.registroNFe.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfe("resumo", {
|
||||
"cnpj": "08187168000160",
|
||||
"id_integracao": "INT-001",
|
||||
"id_usuario": 1
|
||||
}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
class TestNFeInvalido:
|
||||
|
||||
def test_metodo_invalido(self, main_instance):
|
||||
resultado = json.loads(main_instance.nfe("inexistente", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "error"
|
||||
assert "não encontrado" in resultado["message"].lower()
|
||||
146
backup/src/tests/test_nfse.py
Normal file
146
backup/src/tests/test_nfse.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
"""
|
||||
Testes E2E para o serviço NFSe.
|
||||
Testa: POST, GET, DOWNLOAD, DOWNLOAD_XML, CANCELAR,
|
||||
STATUS_CANCELAMENTO, RESUMO, HISTORICO.
|
||||
"""
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dados_nfse():
|
||||
return {
|
||||
"id_usuario": 1,
|
||||
"id_empresa": 10,
|
||||
"idIntegracao": "INT-NFSE-001",
|
||||
"prestador": {"cpfCnpj": "08187168000160"},
|
||||
"tomador": {
|
||||
"cpfCnpj": "08114280956",
|
||||
"razaoSocial": "Tomador Teste",
|
||||
"inscricaoMunicipal": "isento",
|
||||
"email": "tomador@teste.com",
|
||||
"endereco": {
|
||||
"descricaoCidade": "São Paulo",
|
||||
"cep": "01000000",
|
||||
"tipoLogradouro": "Rua",
|
||||
"logradouro": "Teste",
|
||||
"tipoBairro": "Bairro",
|
||||
"codigoCidade": "3550308",
|
||||
"complemento": "",
|
||||
"estado": "SP",
|
||||
"numero": "100",
|
||||
"bairro": "Centro"
|
||||
}
|
||||
},
|
||||
"servico": {
|
||||
"codigo": "1.01",
|
||||
"codigoTributacao": "1.01",
|
||||
"discriminacao": "Serviços de tecnologia",
|
||||
"cnae": "6204000",
|
||||
"iss": {"tipoTributacao": 6, "exigibilidade": 1, "aliquota": 2.5},
|
||||
"valor": {"servico": 1500.0, "descontoCondicionado": 0, "descontoIncondicionado": 0}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestNFSePost:
|
||||
def test_post_nfse_sucesso(self, main_instance, mock_api, dados_nfse):
|
||||
mock_api.post.return_value = {"documents": [{"id": "PLUG-NFSE-001"}]}
|
||||
|
||||
with patch("NFSe.nfse.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfse("post", dados_nfse))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
assert resultado["data"]["documents"][0]["id"] == "PLUG-NFSE-001"
|
||||
|
||||
def test_post_nfse_erro_api(self, main_instance, mock_api, dados_nfse):
|
||||
mock_api.post.return_value = {"error": "CNPJ do prestador não cadastrado"}
|
||||
|
||||
with patch("NFSe.nfse.Api", return_value=mock_api):
|
||||
with pytest.raises(Exception):
|
||||
main_instance.nfse("post", dados_nfse)
|
||||
|
||||
|
||||
class TestNFSeGet:
|
||||
def test_get_nfse_por_id(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"id": "PLUG-NFSE-001", "status": "CONCLUIDO"}
|
||||
|
||||
with patch("NFSe.nfse.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfse("get", {"id": "PLUG-NFSE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_listar_nfse(self, main_instance, banco_teste):
|
||||
"""Listar NFSe sem ID deve buscar do banco."""
|
||||
resultado = json.loads(main_instance.nfse("get", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestNFSeDownload:
|
||||
def test_download_pdf(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"pdf_url": "https://..."}
|
||||
|
||||
with patch("NFSe.nfse.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfse("download", {"id": "PLUG-NFSE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_download_xml(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"xml_url": "https://..."}
|
||||
|
||||
with patch("NFSe.nfse.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfse("download_xml", {"id": "PLUG-NFSE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestNFSeCancelar:
|
||||
def test_cancelar_nfse(self, main_instance, mock_api):
|
||||
mock_api.post.return_value = {"status": "CANCELAMENTO_SOLICITADO"}
|
||||
|
||||
with patch("NFSe.nfse.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfse("cancelar", {"id": "PLUG-NFSE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_status_cancelamento(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"status": "CANCELADO"}
|
||||
|
||||
with patch("NFSe.nfse.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfse("status_cancelamento", {"id": "PLUG-NFSE-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestNFSeResumo:
|
||||
def test_consultar_resumo(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"id": "PLUG-NFSE-001", "numero": "12345"}
|
||||
|
||||
with patch("NFSe.nfse.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfse("resumo", {
|
||||
"id": "PLUG-NFSE-001",
|
||||
"cnpj": "08187168000160",
|
||||
"id_usuario": 1
|
||||
}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestNFSeHistorico:
|
||||
def test_historico_nfse(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = [{"data": "2026-01-01", "status": "CONCLUIDO"}]
|
||||
|
||||
with patch("NFSe.nfse.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.nfse("historico", {
|
||||
"id": "PLUG-NFSE-001",
|
||||
"id_usuario": 1
|
||||
}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestNFSeInvalido:
|
||||
def test_metodo_invalido(self, main_instance):
|
||||
resultado = json.loads(main_instance.nfse("inexistente", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "error"
|
||||
164
backup/src/tests/test_queue.py
Normal file
164
backup/src/tests/test_queue.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import QueueExchange2
|
||||
|
||||
def main():
|
||||
print("Iniciando script de testes da FILA (Queue Exchange)...")
|
||||
|
||||
# Carregando configuração do Redis
|
||||
redis_host = os.getenv('REDIS_HOST', 'localhost')
|
||||
redis_client = RedisClient(host=redis_host, port=6379)
|
||||
try:
|
||||
system_config_data = redis_client.get("system_config")
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis.")
|
||||
return
|
||||
|
||||
system_config = SystemConfig(**json.loads(system_config_data))
|
||||
print(f"[{system_config.origin}] Configuração carregada.")
|
||||
except Exception as e:
|
||||
print(f"Erro ao ler configs: {e}")
|
||||
return
|
||||
|
||||
# Inicializando mensageria de FILA
|
||||
try:
|
||||
# Nota: O QueueExchange2 envia para 'system_service_client'
|
||||
exchange = QueueExchange2(
|
||||
amqps=system_config.amqps,
|
||||
origin=system_config.origin,
|
||||
system="integracao",
|
||||
service="tecnospeed",
|
||||
version="2.1.0",
|
||||
client="#" # '#' para bater com a fila padrão do worker (evita erro de validação length)
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Falha ao iniciar mensageria: {e}")
|
||||
return
|
||||
|
||||
# Configurando criptografia
|
||||
servicekey = system_config.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(serviceid=system_config.serviceid, servicekey=servicekey)
|
||||
|
||||
# Payload (mesmo do teste RPC)
|
||||
payload_interno = {
|
||||
"fila": "empresa",
|
||||
"cliente": "TestQueue",
|
||||
"metodo": "POST",
|
||||
"dados": {
|
||||
"endereco" : {
|
||||
"codigoPais": "1058",
|
||||
"descricaoPais": "Brasil",
|
||||
"tipoLogradouro": "Rua",
|
||||
"logradouro": "T55",
|
||||
"numero": "882",
|
||||
"complemento": "Quadra99 Lote 11/14 Sala 1507",
|
||||
"tipoBairro": "Zona",
|
||||
"bairro": "Setor Bueno",
|
||||
"codigoCidade": "5208707",
|
||||
"descricaoCidade": "Goiania",
|
||||
"estado": "GO",
|
||||
"cep": "74215170"
|
||||
},
|
||||
"nfse" : {
|
||||
"config" : {
|
||||
"rps" : {
|
||||
"lote" : 12,
|
||||
"numeracao" : [
|
||||
{
|
||||
"numero" : 2,
|
||||
"serie" : "CTBZ",
|
||||
"numeracaoAtual" : 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"email" : {
|
||||
"envio" : False
|
||||
},
|
||||
"producao" : True,
|
||||
"envioLimitado" : False,
|
||||
"integracoes" : []
|
||||
},
|
||||
"ativo" : True,
|
||||
"tipoContrato" : 0
|
||||
},
|
||||
"nfe" : {
|
||||
"config" : {
|
||||
"dfe" : {
|
||||
"primeiroNsu" : True
|
||||
},
|
||||
"email" : {
|
||||
"envio" : False
|
||||
},
|
||||
"integracoes" : {
|
||||
"plugStorage" : {
|
||||
"ativo" : False
|
||||
}
|
||||
},
|
||||
"producao" : False,
|
||||
"impressaoFcp" : False,
|
||||
"impressaoPartilha" : True,
|
||||
"versaoManual" : "6.0",
|
||||
"versaoEsquema" : "pl_009",
|
||||
"numeracao" : []
|
||||
},
|
||||
"ativo" : False
|
||||
},
|
||||
"nfce" : {
|
||||
"config" : {
|
||||
"email" : {
|
||||
"envio" : False
|
||||
},
|
||||
"integracoes" : {
|
||||
"plugStorage" : {
|
||||
"ativo" : False
|
||||
}
|
||||
},
|
||||
"producao" : False,
|
||||
"versaoManual" : "6.0",
|
||||
"versaoEsquema" : "pl_009",
|
||||
"numeracao" : []
|
||||
},
|
||||
"ativo" : False
|
||||
},
|
||||
"incentivoFiscal" : False,
|
||||
"incentivadorCultural" : False,
|
||||
"cpfCnpj" : "14358405000100",
|
||||
"inscricaoMunicipal" : "1085328365",
|
||||
"certificado" : "668698592698d7831c758d52",
|
||||
"razaoSocial" : "Teste da SIlva do teste",
|
||||
"simplesNacional" : True,
|
||||
"regimeTributario" : 1,
|
||||
"regimeTributarioEspecial" : 6
|
||||
},
|
||||
"config": {
|
||||
"x_api_key": "2da392a6-79d2-4304-a8b7-959572c7e44d",
|
||||
"user_postgres": "tecnospeed",
|
||||
"pass_postgres": "u7QimG7E!SF//JC2c8",
|
||||
"host_postgres": "postgres",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
},
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br"
|
||||
}
|
||||
|
||||
# Criptografando e enviando
|
||||
exchange.setPayload(json.dumps(payload_interno))
|
||||
|
||||
print("\n--- DEBUG ENVIO ---")
|
||||
print(f"Alvo (targetQueue): integracao_tecnospeed")
|
||||
print(f"Config Enviada: {payload_interno['config']['host_postgres']}:{payload_interno['config']['port_postgres']}")
|
||||
print("-------------------\n")
|
||||
|
||||
print("Disparando mensagem para a FILA...")
|
||||
exchange.sendMsg(json.dumps(payload_interno), service="tecnospeed")
|
||||
print("Mensagem enviada com sucesso. Verifique os logs do worker mainQUEUE.py")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
328
backup/src/tests/test_servicos.py
Normal file
328
backup/src/tests/test_servicos.py
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
import json
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from utils.banco import EmpresaTable, ServicoTable, TomadorTable
|
||||
|
||||
class TestEmpresaPost:
|
||||
@pytest.fixture
|
||||
def dados_empresa(self):
|
||||
return {
|
||||
"id_usuario": 1,
|
||||
"endereco": {
|
||||
"codigoPais": "1058", "descricaoPais": "BRASIL",
|
||||
"tipoLogradouro": "Rua", "logradouro": "Teste", "numero": "123",
|
||||
"complemento": "", "tipoBairro": "Bairro", "bairro": "Centro",
|
||||
"codigoCidade": "5201405", "descricaoCidade": "GOIANIA",
|
||||
"estado": "GO", "cep": "74000000"
|
||||
},
|
||||
"nfse": {
|
||||
"config": {"rps": {"lote": 1, "numeracao": [1]}, "email": {"envio": True},
|
||||
"producao": False, "envioLimitado": False, "integracoes": []},
|
||||
"ativo": True, "tipoContrato": 0
|
||||
},
|
||||
"nfe": {
|
||||
"config": {"dfe": {"primeiroNsu": False}, "email": {"envio": False},
|
||||
"integracoes": {}, "producao": False, "impressaoFcp": False,
|
||||
"impressaoPartilha": False, "versaoManual": "6.0",
|
||||
"versaoEsquema": "PL_009_V4", "numeracao": [1]},
|
||||
"ativo": False
|
||||
},
|
||||
"nfce": {
|
||||
"config": {"email": {"envio": False}, "integracoes": {}, "producao": False,
|
||||
"versaoManual": "6.0", "versaoEsquema": "PL_009_V4", "numeracao": [1]},
|
||||
"ativo": False
|
||||
},
|
||||
"incentivoFiscal": False, "incentivadorCultural": False,
|
||||
"cpfCnpj": "17956143000174", "inscricaoMunicipal": "12345",
|
||||
"certificado": "CERT-UUID", "razaoSocial": "Empresa Teste SA",
|
||||
"simplesNacional": True, "regimeTributario": 1, "regimeTributarioEspecial": 0
|
||||
}
|
||||
|
||||
def test_post_empresa_sucesso(self, main_instance, mock_api, dados_empresa):
|
||||
mock_api.post.return_value = {"_id": "PLUG-EMP-001", "cpfCnpj": "17956143000174"}
|
||||
|
||||
with patch("empresas.empresa.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.empresa("post", dados_empresa))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_post_empresa_erro(self, main_instance, mock_api, dados_empresa):
|
||||
mock_api.post.return_value = {
|
||||
"error": "Erro",
|
||||
"response": {"error": {"message": "CNPJ já cadastrado", "data": []}}
|
||||
}
|
||||
|
||||
with patch("empresas.empresa.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.empresa("post", dados_empresa))
|
||||
|
||||
assert resultado["status"] == "error"
|
||||
|
||||
|
||||
class TestEmpresaGet:
|
||||
def test_get_empresa_por_cnpj(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"_id": "PLUG-EMP-001", "cpfCnpj": "17956143000174", "razaoSocial": "Teste SA"}
|
||||
|
||||
with patch("empresas.empresa.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.empresa("get", {"cnpj": "17956143000174", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_listar_empresas(self, main_instance, mock_api):
|
||||
mock_api.get_paginated.return_value = {"data": [{"cpfCnpj": "111", "razaoSocial": "Emp A"}], "total": 1}
|
||||
|
||||
with patch("empresas.empresa.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.empresa("get", {"id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestEmpresaInvalido:
|
||||
def test_metodo_invalido(self, main_instance):
|
||||
resultado = json.loads(main_instance.empresa("inexistente", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "error"
|
||||
|
||||
|
||||
class TestCertificadoPost:
|
||||
@pytest.fixture
|
||||
def dados_certificado(self):
|
||||
return {
|
||||
"id_usuario": 1,
|
||||
"id_empresa": 10,
|
||||
"arquivo": "CONTEUDO_PFX_BASE64",
|
||||
"senha": "12345678",
|
||||
"expirationAt": "2027-12-31"
|
||||
}
|
||||
|
||||
def test_post_certificado_sucesso(self, main_instance, mock_api, dados_certificado):
|
||||
mock_api.post_form_data.return_value = {"data": {"id": "CERT-001"}}
|
||||
|
||||
with patch("certificado_digital.certificado_digital.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.certificado_digital("post", dados_certificado))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
assert resultado["data"] == "CERT-001"
|
||||
|
||||
|
||||
class TestCertificadoGet:
|
||||
def test_get_certificado_por_id(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"id": "CERT-001", "status": "VALIDO"}
|
||||
|
||||
with patch("certificado_digital.certificado_digital.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.certificado_digital("get", {"id": "CERT-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_listar_certificados(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = [{"id": "CERT-001"}, {"id": "CERT-002"}]
|
||||
|
||||
with patch("certificado_digital.certificado_digital.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.certificado_digital("get", {"id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestCertificadoInvalido:
|
||||
def test_metodo_invalido(self, main_instance):
|
||||
resultado = json.loads(main_instance.certificado_digital("inexistente", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "error"
|
||||
|
||||
class TestServicoPost:
|
||||
"""Testa criação de Serviço na API + banco."""
|
||||
|
||||
@pytest.fixture
|
||||
def dados_servico_completo(self):
|
||||
"""Payload completo do Model Servico com todos campos obrigatórios."""
|
||||
return {
|
||||
"id_usuario": 1,
|
||||
"codigo": "1.01",
|
||||
"idIntegracao": "SERV-INT-001",
|
||||
"discriminacao": "Consultoria em TI",
|
||||
"codigoTributacao": "1.01",
|
||||
"cnae": "6204000",
|
||||
"codigoCidadeIncidencia": "3550308",
|
||||
"descricaoCidadeIncidencia": "São Paulo",
|
||||
"unidade": "UN",
|
||||
"quantidade": 1,
|
||||
"iss": {
|
||||
"tipoTributacao": 6, "exigibilidade": 1,
|
||||
"retido": False, "aliquota": 2.5, "valor": 37.5,
|
||||
"valorRetido": 0, "processoSuspensao": ""
|
||||
},
|
||||
"obra": {"art": "", "codigo": ""},
|
||||
"valor": {
|
||||
"servico": 1500.0, "baseCalculo": 1500.0, "deducoes": 0,
|
||||
"descontoCondicionado": 0, "descontoIncondicionado": 0,
|
||||
"liquido": 1500.0, "unitario": 1500.0, "valorAproximadoTributos": 0
|
||||
},
|
||||
"deducao": {"tipo": 0, "descricao": ""},
|
||||
"retencao": {
|
||||
"pis": {"aliquota": 0, "valor": 0},
|
||||
"cofins": {"aliquota": 0, "valor": 0},
|
||||
"csll": {"aliquota": 0, "valor": 0},
|
||||
"inss": {"aliquota": 0, "valor": 0},
|
||||
"irrf": {"aliquota": 0, "valor": 0},
|
||||
"outrasRetencoes": 0,
|
||||
"cpp": {"aliquota": 0, "valor": 0}
|
||||
},
|
||||
"tributavel": True,
|
||||
"ibpt": {
|
||||
"simplificado": {"aliquota": 8.65},
|
||||
"detalhado": {"aliquota": {"municipal": 2.5, "estadual": 0, "federal": 6.15}}
|
||||
},
|
||||
"responsavelRetencao": ""
|
||||
}
|
||||
|
||||
def test_post_servico_sucesso(self, main_instance, mock_api, dados_servico_completo):
|
||||
mock_api.post.return_value = {"data": {"id": "SERV-001"}}
|
||||
|
||||
with patch("NFSe.Servico.servico.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.servico("post", dados_servico_completo))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestServicoGet:
|
||||
def test_listar_servicos(self, main_instance, banco_teste):
|
||||
"""Deve retornar lista vazia quando não há serviços."""
|
||||
resultado = json.loads(main_instance.servico("get", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_buscar_servico_por_id(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"id": "SERV-001", "discriminacao": "Consultoria"}
|
||||
|
||||
with patch("NFSe.Servico.servico.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.servico("get", {"id": "SERV-001", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestServicoInvalido:
|
||||
def test_metodo_invalido(self, main_instance):
|
||||
resultado = json.loads(main_instance.servico("inexistente", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "error"
|
||||
|
||||
class TestTomadorPost:
|
||||
@pytest.fixture
|
||||
def dados_tomador(self):
|
||||
return {
|
||||
"id_usuario": 1,
|
||||
"cpfCnpj": "08114280956",
|
||||
"inscricaoMunicipal": "isento",
|
||||
"inscricaoEstadual": "",
|
||||
"inscricaoSuframa": "",
|
||||
"indicadorInscricaoEstadual": 9,
|
||||
"razaoSocial": "Tomador Teste LTDA",
|
||||
"nomeFantasia": "Tomador Teste",
|
||||
"endereco": {
|
||||
"tipoLogradouro": "Rua", "logradouro": "Teste", "numero": "100",
|
||||
"complemento": "", "tipoBairro": "Bairro", "bairro": "Centro",
|
||||
"codigoPais": "1058", "descricaoPais": "BRASIL",
|
||||
"codigoCidade": "3550308", "descricaoCidade": "São Paulo",
|
||||
"estado": "SP", "cep": "01000000"
|
||||
},
|
||||
"telefone": {"ddd": "11", "numero": "999999999"},
|
||||
"email": "tomador@teste.com",
|
||||
"orgaoPublico": False
|
||||
}
|
||||
|
||||
def test_post_tomador_sucesso(self, main_instance, mock_api, dados_tomador):
|
||||
mock_api.post.return_value = {"id": "TOM-001", "cpfCnpj": "08114280956"}
|
||||
|
||||
with patch("NFSe.Tomador.tomador.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.tomador("post", dados_tomador))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestTomadorGet:
|
||||
def test_buscar_tomador(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"cpfCnpj": "08114280956", "razaoSocial": "Tomador Teste"}
|
||||
|
||||
with patch("NFSe.Tomador.tomador.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.tomador("get", {"cpf_cnpj": "08114280956", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
|
||||
class TestTomadorInvalido:
|
||||
def test_metodo_invalido(self, main_instance):
|
||||
resultado = json.loads(main_instance.tomador("inexistente", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "error"
|
||||
|
||||
class TestRelatorios:
|
||||
def test_nfe_emitidas(self, main_instance, mock_api):
|
||||
mock_api.get_paginated.return_value = {"data": [{"id": "NFE-1"}], "total": 1}
|
||||
|
||||
with patch("Relatorios.relatorios.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.relatorios("nfe_emitidas", {"id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_nfe_por_cnpj(self, main_instance, mock_api):
|
||||
mock_api.get_paginated.return_value = {"data": [], "total": 0}
|
||||
|
||||
with patch("Relatorios.relatorios.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.relatorios("nfe_por_cnpj", {"cnpj": "08187168000160", "id_usuario": 1}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_nfse_emitidas(self, main_instance, mock_api):
|
||||
mock_api.get_paginated.return_value = {"data": [], "total": 0}
|
||||
|
||||
with patch("Relatorios.relatorios.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.relatorios("nfse_emitidas", {
|
||||
"data_inicial": "2026-01-01", "data_final": "2026-12-31", "id_usuario": 1
|
||||
}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_nfse_por_cnpj(self, main_instance, mock_api):
|
||||
mock_api.get_paginated.return_value = {"data": [], "total": 0}
|
||||
|
||||
with patch("Relatorios.relatorios.Api", return_value=mock_api):
|
||||
resultado = json.loads(main_instance.relatorios("nfse_por_cnpj", {
|
||||
"cnpj": "08187168000160",
|
||||
"data_inicial": "2026-01-01", "data_final": "2026-12-31",
|
||||
"id_usuario": 1
|
||||
}))
|
||||
|
||||
assert resultado["status"] == "ok"
|
||||
|
||||
def test_metodo_invalido(self, main_instance):
|
||||
resultado = json.loads(main_instance.relatorios("inexistente", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "error"
|
||||
|
||||
class TestWebhooks:
|
||||
def test_consultar_webhooks(self, main_instance, mock_api):
|
||||
mock_api.get.return_value = {"url": " ", "method": "POST"}
|
||||
|
||||
with patch("webhooks.webhooks.Api", return_value=mock_api):
|
||||
main_instance.webhooks("get", {"cnpj": "08187168000160", "id_usuario": 1})
|
||||
|
||||
def test_criar_webhook(self, main_instance, mock_api):
|
||||
mock_api.post.return_value = {"status": "ok"}
|
||||
|
||||
with patch("webhooks.webhooks.Api", return_value=mock_api):
|
||||
main_instance.webhooks("post", {"cnpj": "08187168000160", "url": " ", "id_usuario": 1})
|
||||
|
||||
def test_deletar_webhook(self, main_instance, mock_api):
|
||||
mock_api.delete.return_value = {"status": "ok"}
|
||||
|
||||
with patch("webhooks.webhooks.Api", return_value=mock_api):
|
||||
main_instance.webhooks("delete", {"cnpj": "08187168000160", "id_usuario": 1})
|
||||
|
||||
def test_atualizar_webhook(self, main_instance, mock_api):
|
||||
mock_api.put.return_value = {"status": "ok"}
|
||||
|
||||
with patch("webhooks.webhooks.Api", return_value=mock_api):
|
||||
main_instance.webhooks("put", {"cnpj": "08187168000160", "url": " ", "id_usuario": 1})
|
||||
|
||||
def test_testar_webhook(self, main_instance, mock_api):
|
||||
mock_api.post.return_value = {"status": "ok", "message": "Webhook testado"}
|
||||
|
||||
with patch("webhooks.webhooks.Api", return_value=mock_api):
|
||||
main_instance.webhooks("test", {"cnpj": "08187168000160", "id_usuario": 1})
|
||||
|
||||
def test_metodo_invalido(self, main_instance):
|
||||
resultado = json.loads(main_instance.webhooks("inexistente", {"id_usuario": 1}))
|
||||
assert resultado["status"] == "error"
|
||||
80
backup/src/tests/test_simplified_payload.py
Normal file
80
backup/src/tests/test_simplified_payload.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import RPCExchange2
|
||||
|
||||
def run_rpc_test():
|
||||
redis_host = os.getenv('REDIS_HOST', 'localhost')
|
||||
redis_client = RedisClient(host=redis_host, port=6379)
|
||||
try:
|
||||
config_data = redis_client.get("system_config")
|
||||
sc = SystemConfig(**json.loads(config_data))
|
||||
except Exception as e:
|
||||
print(f"Erro ao carregar configs: {e}")
|
||||
return
|
||||
|
||||
config_rpc = {
|
||||
"x_api_key": "2da392a6-79d2-4304-a8b7-959572c7e44d",
|
||||
"user_postgres": "tecnospeed",
|
||||
"pass_postgres": "u7QimG7E%21SF%2F%2FJC2c8",
|
||||
"host_postgres": "banco",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
}
|
||||
|
||||
exchange = RPCExchange2(sc.amqps, sc.origin, "integracao", "tecnospeed", "2.1.0", client="#")
|
||||
servicekey = sc.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(sc.serviceid, servicekey)
|
||||
|
||||
# Payload "simplificado" do front-end
|
||||
full_payload = {
|
||||
"fila": "empresa",
|
||||
"metodo": "POST",
|
||||
"cliente": "tray",
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br",
|
||||
"config": config_rpc,
|
||||
"dados": {
|
||||
"cpfCnpj": "94074986000198",
|
||||
"razaoSocial": "Tech nux",
|
||||
"nomeFantasia": "Tech nux ltda",
|
||||
"inscricaoMunicipal": "0001",
|
||||
"simplesNacional": True,
|
||||
"regimeTributario": 1,
|
||||
"regimeTributarioEspecial": 1,
|
||||
"incentivoCultural": False,
|
||||
"incentivoFiscal": True,
|
||||
"certificado": "cert-mock-01",
|
||||
"endereco": {
|
||||
"codigoPais": "1058",
|
||||
"descricaoPais": "BRASIL",
|
||||
"tipoLogradouro": "Avenida",
|
||||
"logradouro": "Avenida",
|
||||
"numero": "123",
|
||||
"complemento": "Casa",
|
||||
"tipoBairro": "Bairro",
|
||||
"bairro": "Centro",
|
||||
"codigoCidade": "5200175",
|
||||
"descricaoCidade": "ÁGUA FRIA DE GOIÁS",
|
||||
"estado": "GO",
|
||||
"cep": "68907-460"
|
||||
},
|
||||
"nfe": { "ativo": True },
|
||||
"nfse": { "ativo": False }
|
||||
}
|
||||
}
|
||||
|
||||
print("Enviando payload via RPC...")
|
||||
exchange.setPayload(json.dumps(full_payload))
|
||||
resposta = exchange.callRPC(json.dumps(full_payload), timeout=30)
|
||||
|
||||
print("\nRESPOSTA DO SERVIDOR (RPC):")
|
||||
print(resposta)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_rpc_test()
|
||||
88
backup/src/tests/test_upload_certificado.py
Normal file
88
backup/src/tests/test_upload_certificado.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Carrega variáveis de ambiente
|
||||
load_dotenv()
|
||||
|
||||
# Adiciona o diretório atual ao path para importar as classes locais
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import QueueExchange2
|
||||
|
||||
# Dados do Certificado (Preenchidos dinamicamente)
|
||||
PFX_BASE64 = " "
|
||||
PFX_PASSWORD = str(sys.argv[1]) if len(sys.argv) > 1 else " "
|
||||
|
||||
def main():
|
||||
print(f"Iniciando teste de upload de certificado para senha: {PFX_PASSWORD}")
|
||||
|
||||
# 1. Carregando configuração do Redis
|
||||
redis_host = os.getenv('REDIS_HOST', 'localhost')
|
||||
redis_client = RedisClient(host=redis_host, port=6379)
|
||||
try:
|
||||
system_config_data = redis_client.get("system_config")
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis.")
|
||||
return
|
||||
|
||||
system_config = SystemConfig(**json.loads(system_config_data))
|
||||
except Exception as e:
|
||||
print(f"Erro ao ler configs do Redis: {e}")
|
||||
return
|
||||
|
||||
# 2. Inicializando QueueExchange2
|
||||
try:
|
||||
exchange = QueueExchange2(
|
||||
amqps=system_config.amqps,
|
||||
origin=system_config.origin,
|
||||
system="integracao",
|
||||
service="tecnospeed",
|
||||
version="1.0.0",
|
||||
client="#"
|
||||
)
|
||||
|
||||
servicekey = system_config.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(serviceid=system_config.serviceid, servicekey=servicekey)
|
||||
except Exception as e:
|
||||
print(f"Falha ao iniciar mensageria: {e}")
|
||||
return
|
||||
|
||||
# 3. Montando o payload interno
|
||||
internal_payload = {
|
||||
"fila": "certificado_digital",
|
||||
"cliente": "UploadTest",
|
||||
"metodo": "POST",
|
||||
"dados": {
|
||||
"arquivo": PFX_BASE64,
|
||||
"senha": PFX_PASSWORD,
|
||||
"id_empresa": "c19d874447b1kxlapg1xv783",
|
||||
"id_usuario": 1,
|
||||
"expirationAt": "2027-04-13"
|
||||
},
|
||||
"config": {
|
||||
"x_api_key": os.getenv("PLUGNOTAS_API_KEY"),
|
||||
"user_postgres": os.getenv("POSTGRES_USER", "tecnospeed"),
|
||||
"pass_postgres": os.getenv("POSTGRES_PASSWORD", "u7QimG7E!SF//JC2c8"),
|
||||
"host_postgres": "postgres",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": os.getenv("POSTGRES_DB", "tecnospeed")
|
||||
},
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br"
|
||||
}
|
||||
|
||||
# 4. Enviando com criptografia (hmac/payload)
|
||||
try:
|
||||
msg_json = json.dumps(internal_payload)
|
||||
exchange.setPayload(msg_json)
|
||||
exchange.sendMsg(msg_json, service="tecnospeed")
|
||||
print("Mensagem enviada com sucesso (criptografada)!")
|
||||
except Exception as e:
|
||||
print(f"Erro ao enviar mensagem: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1041
backup/src/utils/Exchange2.py
Normal file
1041
backup/src/utils/Exchange2.py
Normal file
File diff suppressed because it is too large
Load diff
158
backup/src/utils/api.py
Normal file
158
backup/src/utils/api.py
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
import requests
|
||||
|
||||
# Wrapper base para realizar requests (GET/POST/PUT/DELETE) à API da PlugNotas de forma padronizada
|
||||
class Api:
|
||||
def __init__(self, api_key):
|
||||
self.api_key = api_key
|
||||
|
||||
def get(self, url):
|
||||
try:
|
||||
headers = {
|
||||
"x-api-key": self.api_key
|
||||
}
|
||||
response = requests.get(url, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
resp_data = {}
|
||||
if 'response' in locals() and response is not None:
|
||||
try:
|
||||
resp_data = response.json()
|
||||
except ValueError:
|
||||
resp_data = response.text
|
||||
return {"error": str(e), "response": resp_data}
|
||||
|
||||
def get_paginated(self, url, max_pages=100):
|
||||
try:
|
||||
headers = {
|
||||
"x-api-key": self.api_key
|
||||
}
|
||||
all_data = []
|
||||
current_url = url
|
||||
page = 0
|
||||
|
||||
while current_url and page < max_pages:
|
||||
separator = "&" if "?" in current_url else "?"
|
||||
request_url = current_url if page == 0 else current_url
|
||||
|
||||
response = requests.get(request_url, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if isinstance(result, list):
|
||||
all_data.extend(result)
|
||||
break
|
||||
elif isinstance(result, dict):
|
||||
page_data = result.get("data", [])
|
||||
if isinstance(page_data, list):
|
||||
all_data.extend(page_data)
|
||||
elif page_data:
|
||||
all_data.append(page_data)
|
||||
|
||||
hash_proxima = result.get("hashProximaPagina")
|
||||
if hash_proxima:
|
||||
separator = "&" if "?" in url else "?"
|
||||
current_url = f"{url}{separator}hashProximaPagina={hash_proxima}"
|
||||
page += 1
|
||||
else:
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
return {"data": all_data, "total_paginas": page + 1, "total_registros": len(all_data)}
|
||||
except requests.exceptions.RequestException as e:
|
||||
resp_data = {}
|
||||
if 'response' in locals() and response is not None:
|
||||
try:
|
||||
resp_data = response.json()
|
||||
except ValueError:
|
||||
resp_data = response.text
|
||||
return {"error": str(e), "response": resp_data}
|
||||
|
||||
def post(self, url, data):
|
||||
try:
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": self.api_key
|
||||
}
|
||||
response = requests.post(url, headers=headers, json=data, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
resp_data = {}
|
||||
if 'response' in locals() and response is not None:
|
||||
try:
|
||||
resp_data = response.json()
|
||||
except ValueError:
|
||||
resp_data = response.text
|
||||
return {"error": str(e), "response": resp_data}
|
||||
|
||||
def put(self, url, data):
|
||||
try:
|
||||
headers = {
|
||||
"x-api-key": self.api_key
|
||||
}
|
||||
response = requests.put(url, headers=headers, json=data, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def patch(self, url, data):
|
||||
try:
|
||||
headers = {
|
||||
"x-api-key": self.api_key
|
||||
}
|
||||
response = requests.patch(url, headers=headers, json=data, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def delete(self, url):
|
||||
try:
|
||||
headers = {
|
||||
"x-api-key": self.api_key
|
||||
}
|
||||
response = requests.delete(url, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def post_file(self, url, file):
|
||||
try:
|
||||
headers = {
|
||||
"x-api-key": self.api_key
|
||||
}
|
||||
response = requests.post(url, headers=headers, files=file, timeout=60)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def post_form_data(self, url, data):
|
||||
try:
|
||||
headers = {
|
||||
"x-api-key": self.api_key
|
||||
}
|
||||
response = requests.post(url, headers=headers, files=data, timeout=60)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
def put_form_data(self, url, data):
|
||||
try:
|
||||
headers = {
|
||||
"x-api-key": self.api_key
|
||||
}
|
||||
response = requests.put(url, headers=headers, files=data, timeout=60)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
# api = Api("2da392a6-79d2-4304-a8b7-959572c7e44d")
|
||||
# response = api.get("https://api.sandbox.plugnotas.com.br/certificado")
|
||||
# print(response)
|
||||
241
backup/src/utils/banco.py
Normal file
241
backup/src/utils/banco.py
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
from datetime import datetime
|
||||
import uuid
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Text, create_engine, Boolean
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class CertificadoDigitalTable(Base):
|
||||
__tablename__ = 'certificado_digital'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
id_tecnospeed = Column(String(50), nullable=False)
|
||||
certificado = Column(Text, nullable=False)
|
||||
createdAt = Column(DateTime, default=datetime.now)
|
||||
updatedAt = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
deletedAt = Column(DateTime, nullable=True)
|
||||
expirationAt = Column(DateTime, nullable=False)
|
||||
senha = Column(String(50), nullable=False)
|
||||
status = Column(Boolean, nullable=False)
|
||||
id_empresa = Column(String(50), nullable=False)
|
||||
id_usuario = Column(Integer, nullable=False)
|
||||
email = Column(String(255), nullable=True)
|
||||
|
||||
class EnterpriseTable(Base):
|
||||
__tablename__ = 'Enterprise'
|
||||
id = Column(String(50), primary_key=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
cnpj = Column(String(50), nullable=False)
|
||||
banco = Column(Text, nullable=True)
|
||||
email = Column(String(255), nullable=True)
|
||||
createdAt = Column(DateTime, default=datetime.now)
|
||||
updatedAt = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
deletedAt = Column(DateTime, nullable=True)
|
||||
status = Column(Boolean, nullable=False, default=True)
|
||||
|
||||
class LogoTable(Base):
|
||||
__tablename__ = 'logo'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
logo = Column(Text, nullable=False)
|
||||
id_empresa = Column(String(50), nullable=False)
|
||||
createdAt = Column(DateTime, default=datetime.now)
|
||||
updatedAt = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
deletedAt = Column(DateTime, nullable=True)
|
||||
status = Column(Boolean, nullable=False)
|
||||
|
||||
class LogTable(Base):
|
||||
__tablename__ = 'log'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
id_usuario = Column(Integer, nullable=False)
|
||||
createdAt = Column(DateTime, default=datetime.now)
|
||||
updatedAt = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
deletedAt = Column(DateTime, nullable=True)
|
||||
acao = Column(String(50), nullable=False)
|
||||
tabela = Column(String(50), nullable=False)
|
||||
id_registro = Column(String(50), nullable=False)
|
||||
json = Column(Text, nullable=False)
|
||||
status = Column(String(20), nullable=False, default='INICIO')
|
||||
mensagem = Column(Text, nullable=False, default='')
|
||||
cliente = Column(String(100), nullable=True)
|
||||
correlation_id = Column(String(50), nullable=True)
|
||||
|
||||
class ServicoTable(Base):
|
||||
__tablename__ = 'servico'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
id_tecnospeed = Column(String(50), nullable=False)
|
||||
descricao = Column(Text, nullable=False)
|
||||
id_usuario = Column(Integer, nullable=False)
|
||||
createdAt = Column(DateTime, default=datetime.now)
|
||||
updatedAt = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
deletedAt = Column(DateTime, nullable=True)
|
||||
status = Column(Boolean, nullable=False)
|
||||
|
||||
class TomadorTable(Base):
|
||||
__tablename__ = 'tomador'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
id_tecnospeed = Column(String(50), nullable=False)
|
||||
nome = Column(String(50), nullable=False)
|
||||
cpf_cnpj = Column(String(50), nullable=False)
|
||||
email = Column(String(50), nullable=False)
|
||||
telefone = Column(String(50), nullable=False)
|
||||
id_usuario = Column(Integer, nullable=False)
|
||||
createdAt = Column(DateTime, default=datetime.now)
|
||||
updatedAt = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
deletedAt = Column(DateTime, nullable=True)
|
||||
status = Column(Boolean, nullable=False)
|
||||
|
||||
class NFSeTable(Base):
|
||||
__tablename__ = 'nfse'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
id_tecnospeed = Column(String(50), nullable=False)
|
||||
id_empresa = Column(String(50), nullable=False)
|
||||
id_usuario = Column(Integer, nullable=False)
|
||||
createdAt = Column(DateTime, default=datetime.now)
|
||||
updatedAt = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
deletedAt = Column(DateTime, nullable=True)
|
||||
status = Column(Boolean, nullable=False)
|
||||
|
||||
class NFeTable(Base):
|
||||
__tablename__ = 'nfe'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
id_tecnospeed = Column(String(50), nullable=False)
|
||||
id_empresa = Column(String(50), nullable=False)
|
||||
id_usuario = Column(Integer, nullable=False)
|
||||
createdAt = Column(DateTime, default=datetime.now)
|
||||
updatedAt = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
deletedAt = Column(DateTime, nullable=True)
|
||||
status = Column(Boolean, nullable=False)
|
||||
|
||||
# Cache de motores criados pelo SQLAlchemy por URL de conexao
|
||||
_engines = {}
|
||||
|
||||
# Abstração de conexão base e de persistência de instâncias de metadados relacionais usando PostgreSQL
|
||||
class Banco:
|
||||
def __init__(self, host, port, user, password, database):
|
||||
conn_str = f'postgresql://{user}:{password}@{host}:{port}/{database}'
|
||||
if conn_str not in _engines:
|
||||
_engines[conn_str] = create_engine(
|
||||
conn_str,
|
||||
pool_pre_ping=True,
|
||||
pool_recycle=3600
|
||||
)
|
||||
self.engine = _engines[conn_str]
|
||||
self.Session = sessionmaker(bind=self.engine)
|
||||
self.session = self.Session()
|
||||
|
||||
def close(self):
|
||||
"""Encerra a sessão SQLAlchemy atual liberando do pooler."""
|
||||
if hasattr(self, 'session') and self.session is not None:
|
||||
self.session.close()
|
||||
|
||||
def create_tables(self):
|
||||
"""Cria as tabelas no banco de dados."""
|
||||
try:
|
||||
Base.metadata.create_all(self.engine)
|
||||
except SQLAlchemyError as e:
|
||||
print(f"Erro ao criar tabelas (SQLAlchemy): {e}")
|
||||
except Exception as e:
|
||||
print(f"Erro ao criar tabelas: {e}")
|
||||
|
||||
def add(self, obj):
|
||||
"""Adiciona um novo registro no banco."""
|
||||
try:
|
||||
self.session.add(obj)
|
||||
self.session.commit()
|
||||
return obj
|
||||
except SQLAlchemyError as e:
|
||||
self.session.rollback()
|
||||
print(f"Erro ao adicionar registro (SQLAlchemy): {e}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
self.session.rollback()
|
||||
print(f"Erro ao adicionar registro: {e}")
|
||||
raise e
|
||||
|
||||
def get(self, model, obj_id):
|
||||
"""Busca um registro pelo ID."""
|
||||
try:
|
||||
return self.session.query(model).get(obj_id)
|
||||
except SQLAlchemyError as e:
|
||||
self.session.rollback()
|
||||
print(f"Erro ao buscar registro (SQLAlchemy): {e}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
self.session.rollback()
|
||||
print(f"Erro ao buscar registro: {e}")
|
||||
raise e
|
||||
|
||||
def get_all(self, model):
|
||||
"""Retorna todos os registros de um modelo."""
|
||||
try:
|
||||
return self.session.query(model).all()
|
||||
except SQLAlchemyError as e:
|
||||
print(f"Erro ao buscar registros (SQLAlchemy): {e}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
print(f"Erro ao buscar registros: {e}")
|
||||
raise e
|
||||
|
||||
def update(self, obj):
|
||||
"""Atualiza um registro no banco."""
|
||||
try:
|
||||
self.session.commit()
|
||||
return obj
|
||||
except SQLAlchemyError as e:
|
||||
self.session.rollback()
|
||||
print(f"Erro ao atualizar registro (SQLAlchemy): {e}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
self.session.rollback()
|
||||
print(f"Erro ao atualizar registro: {e}")
|
||||
raise e
|
||||
|
||||
def delete(self, obj):
|
||||
"""Deleta um registro do banco."""
|
||||
try:
|
||||
self.session.delete(obj)
|
||||
self.session.commit()
|
||||
return True
|
||||
except SQLAlchemyError as e:
|
||||
self.session.rollback()
|
||||
print(f"Erro ao deletar registro (SQLAlchemy): {e}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
self.session.rollback()
|
||||
print(f"Erro ao deletar registro: {e}")
|
||||
raise e
|
||||
|
||||
def rollback(self):
|
||||
"""Desfaz as alterações pendentes."""
|
||||
try:
|
||||
self.session.rollback()
|
||||
except SQLAlchemyError as e:
|
||||
print(f"Erro ao desfazer alterações (SQLAlchemy): {e}")
|
||||
except Exception as e:
|
||||
print(f"Erro ao desfazer alterações: {e}")
|
||||
|
||||
def logar_sistema(self, id_usuario, acao, tabela, id_registro=0, json="{}", status="INICIO", mensagem="", cliente=None, correlation_id=None):
|
||||
"""Registra uma ação no log do sistema."""
|
||||
try:
|
||||
log = LogTable(
|
||||
id_usuario=id_usuario,
|
||||
acao=acao,
|
||||
tabela=tabela,
|
||||
id_registro=id_registro,
|
||||
json=json,
|
||||
status=status,
|
||||
mensagem=mensagem,
|
||||
cliente=cliente,
|
||||
correlation_id=correlation_id
|
||||
)
|
||||
self.add(log)
|
||||
except SQLAlchemyError as e:
|
||||
print(f"Erro ao logar ação (SQLAlchemy): {e}")
|
||||
except Exception as e:
|
||||
print(f"Erro ao logar ação: {e}")
|
||||
|
||||
@staticmethod
|
||||
def gerar_correlation_id():
|
||||
"""Gera um ID único para rastrear toda a cadeia de uma operação."""
|
||||
return str(uuid.uuid4())[:8]
|
||||
|
||||
35
backup/src/utils/enviaListerner.py
Normal file
35
backup/src/utils/enviaListerner.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import requests
|
||||
import json
|
||||
import os
|
||||
|
||||
class enviaListerner:
|
||||
|
||||
def __init__(self, logger=None):
|
||||
self.url = os.getenv("LISTENER_INTERNAL_URL", "http://webhook_listener:8000/listenerPlugnotas")
|
||||
self.logger = logger
|
||||
|
||||
def notificar_emissao(self, id_tecnospeed: str, documento: str, cnpj: str, status: str = "PROCESSANDO"):
|
||||
"""
|
||||
Envia um POST para o listener local para sincronização imediata.
|
||||
"""
|
||||
payload = {
|
||||
"id": id_tecnospeed,
|
||||
"status": status,
|
||||
"documento": documento,
|
||||
"emitente": cnpj
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.url, json=payload, timeout=5)
|
||||
|
||||
if self.logger:
|
||||
if response.status_code in [200, 201]:
|
||||
self.logger.log_logs(f"Notificação interna enviada com sucesso - ID: {id_tecnospeed} | Status: {status}")
|
||||
else:
|
||||
self.logger.log_errors(f"Falha na notificação interna - Status: {response.status_code} | Resposta: {response.text}")
|
||||
|
||||
return response.status_code in [200, 201]
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.log_errors(f"Erro ao tentar notificar listener interno: {e}")
|
||||
return False
|
||||
127
backup/src/utils/exchangeModels.py
Normal file
127
backup/src/utils/exchangeModels.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
|
||||
__version__ = "1.0.0 - 18/02/2025" # VERSAO PARA TODOS OS SISTEMAS TRANSMITINDO PARA O SERVIÇO LOGGER_CONTROLLER DO SG_IC2
|
||||
__version__ = "1.0.1 - 15/01/2026 - S.D.G. - Ajustes"
|
||||
__version__ = "1.0.2 - 23/01/2026 - S.D.G. - ForwardCompatibleModel & SerializeAsAny"
|
||||
from pydantic import BaseModel, field_validator, ValidationInfo, Field, ConfigDict, SerializeAsAny
|
||||
from enum import StrEnum
|
||||
from typing import Union, Any, Optional
|
||||
|
||||
class ForwardCompatibleModel(BaseModel):
|
||||
"""
|
||||
Base para todos os modelos que permite campos extras.
|
||||
O Pydantic V2 gerencia automaticamente o dicionário 'model_extra'.
|
||||
"""
|
||||
model_config = ConfigDict(extra='allow')
|
||||
|
||||
class RealmType(StrEnum):
|
||||
LOGS = "LOGS"
|
||||
METRICS = "METRICS"
|
||||
STATUS = "STATUS"
|
||||
ERRORS = "ERRORS"
|
||||
OPERATIONAL = "OPERATIONAL"
|
||||
|
||||
class OpStatus(StrEnum):
|
||||
ACTIVE = "ACTIVE"
|
||||
OK = "OK"
|
||||
ERROR = "ERROR"
|
||||
|
||||
class ICLogs(ForwardCompatibleModel):
|
||||
log_date : str
|
||||
service_date : str
|
||||
service : str
|
||||
versionstr : str
|
||||
origin : str
|
||||
enterprise_id : str
|
||||
enterprise_name : str
|
||||
message : str
|
||||
tenant : Optional[str] = Field(default='#', description="Tenant ou '#' para global")
|
||||
|
||||
class ICErrors(ForwardCompatibleModel):
|
||||
log_date : str
|
||||
service_date : str
|
||||
service : str
|
||||
versionstr : str
|
||||
origin : str
|
||||
enterprise_id : str
|
||||
enterprise_name : str
|
||||
message : str
|
||||
tenant : Optional[str] = Field(default='#', description="Tenant ou '#' para global")
|
||||
|
||||
class ICMetrics(ForwardCompatibleModel):
|
||||
metric_date : str
|
||||
metric : str
|
||||
metric_name : str
|
||||
value : str
|
||||
unit : str
|
||||
tenant : Optional[str] = Field(default='#', description="Tenant ou '#' para global")
|
||||
|
||||
class ICOperational(ForwardCompatibleModel):
|
||||
log_date : str # YYYY-MM-DD HH:MM:S
|
||||
service_date : str # YYYY-MM-DD HH:MM:S
|
||||
service_name : str
|
||||
versionstr : str
|
||||
origin : str
|
||||
enterprise_id : str
|
||||
enterprise_name : str
|
||||
status : OpStatus
|
||||
step_status : str
|
||||
message : str
|
||||
|
||||
class ICStatus(ForwardCompatibleModel):
|
||||
service : str
|
||||
versionstr : str
|
||||
system_name : str
|
||||
startup_date : str
|
||||
access_date : str
|
||||
origin : str
|
||||
sid : Optional[str] = ""
|
||||
task_count : Optional[int] = 0
|
||||
cpu : Optional[float] = 0.0
|
||||
memory : Optional[float] = 0.0
|
||||
disk : Optional[float] = 0.0
|
||||
network : Optional[float] = 0.0
|
||||
|
||||
# Modelo polimórfico gerador de metadados JSON para o Exchange de Logs do Sistema Inteirador
|
||||
class InfoConnector(ForwardCompatibleModel):
|
||||
realm: RealmType
|
||||
|
||||
# SerializeAsAny garante que se 'messageIC' for uma subclasse (como 'Operation'),
|
||||
# os campos extras da subclasse (como 'reserva_id') aparecerão no JSON final.
|
||||
messageIC: SerializeAsAny[Union[ICLogs, ICErrors, ICMetrics, ICStatus, ICOperational]]
|
||||
|
||||
@field_validator('messageIC', mode='before')
|
||||
@classmethod
|
||||
def validate_schema(cls, v: Any, info: ValidationInfo) -> Any:
|
||||
realm = info.data.get("realm")
|
||||
if realm is None:
|
||||
# Se realm não existe, o próprio Pydantic lançará erro de validação do campo 'realm'
|
||||
return v
|
||||
|
||||
# Mapeamento de Realm para Classe Esperada
|
||||
target_map = {
|
||||
RealmType.LOGS : ICLogs,
|
||||
RealmType.ERRORS : ICErrors,
|
||||
RealmType.METRICS : ICMetrics,
|
||||
RealmType.STATUS : ICStatus,
|
||||
RealmType.OPERATIONAL: ICOperational,
|
||||
}
|
||||
|
||||
expected_class = target_map.get(realm)
|
||||
if not expected_class:
|
||||
raise ValueError(f"Invalid realm: {realm}")
|
||||
|
||||
# Se for um dicionário (vindo de um JSON), validamos contra a classe mapeada
|
||||
if isinstance(v, dict):
|
||||
return expected_class.model_validate(v)
|
||||
|
||||
# Se já for um objeto instanciado (como no seu Builder), verificamos a herança
|
||||
if not isinstance(v, expected_class):
|
||||
raise ValueError(f"Expected {expected_class.__name__} for realm {realm}, got {type(v).__name__}")
|
||||
|
||||
return v
|
||||
|
||||
model_config = ConfigDict(
|
||||
extra="allow",
|
||||
frozen=False,
|
||||
str_strip_whitespace=True
|
||||
)
|
||||
107
backup/src/utils/redis.py
Normal file
107
backup/src/utils/redis.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import redis
|
||||
|
||||
# Fornece mecanismos de Caching e Persistência Volátil/PubSub na aplicação usando Redis
|
||||
class RedisClient:
|
||||
def __init__(self, host='localhost', port=6379, db=0):
|
||||
"""
|
||||
Inicializa o cliente Redis com as configurações fornecidas.
|
||||
"""
|
||||
self.client = redis.Redis(host=host, port=port, db=db, decode_responses=True)
|
||||
|
||||
def set(self, key, value):
|
||||
"""
|
||||
Define um valor para uma chave no Redis.
|
||||
"""
|
||||
self.client.set(key, value)
|
||||
|
||||
def setex(self, key, value, ex):
|
||||
"""
|
||||
Define um valor para uma chave no Redis com um tempo de expiração em segundos.
|
||||
"""
|
||||
self.client.set(key, value, ex)
|
||||
|
||||
def get(self, key):
|
||||
"""
|
||||
Obtém o valor de uma chave no Redis.
|
||||
"""
|
||||
return self.client.get(key)
|
||||
|
||||
def get_keys(self, pattern="*"):
|
||||
"""
|
||||
Obtém todas as chaves que correspondem a um padrão.
|
||||
"""
|
||||
return self.client.keys(pattern)
|
||||
|
||||
def get_all(self):
|
||||
"""
|
||||
Obtém todas as chaves e seus valores no Redis.
|
||||
"""
|
||||
keys = self.client.keys('*')
|
||||
return {key: self.client.get(key) for key in keys}
|
||||
|
||||
def delete(self, key):
|
||||
"""
|
||||
Remove uma chave do Redis.
|
||||
"""
|
||||
self.client.delete(key)
|
||||
|
||||
def exists(self, key):
|
||||
"""
|
||||
Verifica se uma chave existe no Redis.
|
||||
Retorna True se a chave existir, caso contrário, False.
|
||||
"""
|
||||
return self.client.exists(key) > 0
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Fecha a conexão com o Redis.
|
||||
"""
|
||||
self.client.close()
|
||||
|
||||
def set_expiration(self, key, value, seconds):
|
||||
"""
|
||||
Define um valor para uma chave e configura um tempo de expiração em segundos.
|
||||
"""
|
||||
self.client.set(key, value, ex=seconds)
|
||||
|
||||
def increment(self, key, amount=1):
|
||||
"""
|
||||
Incrementa o valor de uma chave numérica no Redis.
|
||||
"""
|
||||
return self.client.incr(key, amount)
|
||||
|
||||
def decrement(self, key, amount=1):
|
||||
"""
|
||||
Decrementa o valor de uma chave numérica no Redis.
|
||||
"""
|
||||
return self.client.decr(key, amount)
|
||||
|
||||
def hset(self, name, key, value):
|
||||
"""
|
||||
Define um campo em um hash no Redis.
|
||||
"""
|
||||
self.client.hset(name, key, value)
|
||||
|
||||
def hget(self, name, key):
|
||||
"""
|
||||
Obtém o valor de um campo em um hash no Redis.
|
||||
"""
|
||||
return self.client.hget(name, key)
|
||||
|
||||
def hgetall(self, name):
|
||||
"""
|
||||
Obtém todos os campos e valores de um hash no Redis.
|
||||
"""
|
||||
return self.client.hgetall(name)
|
||||
|
||||
def expire(self, key, seconds):
|
||||
"""
|
||||
Define um tempo de expiração para uma chave existente.
|
||||
"""
|
||||
self.client.expire(key, seconds)
|
||||
|
||||
def ttl(self, key):
|
||||
"""
|
||||
Obtém o tempo restante de vida (TTL) de uma chave.
|
||||
"""
|
||||
return self.client.ttl(key)
|
||||
64
backup/src/webhooks/webhooks.py
Normal file
64
backup/src/webhooks/webhooks.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
from utils.api import Api
|
||||
from utils.banco import Banco
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
import json
|
||||
|
||||
# Serviço responsável por vincular rotas (webhooks) por CNPJ onde a PlugNotas enviará notificações
|
||||
class Webhooks:
|
||||
def __init__(self, url_base, api_key, banco:Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/empresa/"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def consultar_webhooks(self, cnpj):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}{cnpj}/webhook")
|
||||
self._log(RealmType.OPERATIONAL, f"Webhook consultado - CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao consultar webhook: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def criar_webhook(self, cnpj, url):
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}{cnpj}/webhook", {"url": url})
|
||||
self._log(RealmType.OPERATIONAL, f"Webhook criado - CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao criar webhook: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def deletar_webhook(self, cnpj):
|
||||
try:
|
||||
resposta = self.api.delete(f"{self.url}{cnpj}/webhook")
|
||||
self._log(RealmType.OPERATIONAL, f"Webhook deletado - CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao deletar webhook: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def atualizar_webhook(self, cnpj, url):
|
||||
try:
|
||||
resposta = self.api.put(f"{self.url}{cnpj}/webhook", {"url": url,"method": "POST"})
|
||||
self._log(RealmType.OPERATIONAL, f"Webhook atualizado - CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao atualizar webhook: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def testar_webhook(self, cnpj):
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}{cnpj}/webhook/verify", {})
|
||||
self._log(RealmType.OPERATIONAL, f"Webhook testado - CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao testar webhook: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
52
backup/test_payload.py
Normal file
52
backup/test_payload.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import json
|
||||
from src.Models.payload import validate_payload
|
||||
|
||||
payload = {
|
||||
"fila": "empresa",
|
||||
"metodo": "POST",
|
||||
"cliente": "tray",
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br",
|
||||
"config": {
|
||||
"x_api_key": "2da392a6-79d2-4304-a8b7-959572c7e44d",
|
||||
"user_postgres": "tecnospeed",
|
||||
"pass_postgres": "u7QimG7E%21SF%2F%2FJC2c8",
|
||||
"host_postgres": "postgres",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
},
|
||||
"dados": {
|
||||
"cpfCnpj": "94074986000198",
|
||||
"razaoSocial": "Tech nux",
|
||||
"nomeFantasia": "Tech nux ltda",
|
||||
"inscricaoMunicipal": "0001",
|
||||
"simplesNacional": True,
|
||||
"regimeTributario": 1,
|
||||
"regimeTributarioEspecial": 1,
|
||||
"incentivadorCultural": False,
|
||||
"incentivoFiscal": True,
|
||||
"certificado": "cert-mock-01",
|
||||
"endereco": {
|
||||
"codigoPais": "1058",
|
||||
"descricaoPais": "BRASIL",
|
||||
"tipoLogradouro": "Avenida",
|
||||
"logradouro": "Avenida",
|
||||
"numero": "123",
|
||||
"complemento": "Casa",
|
||||
"tipoBairro": "Bairro",
|
||||
"bairro": "Centro",
|
||||
"codigoCidade": "5200175",
|
||||
"descricaoCidade": "ÁGUA FRIA DE GOIÁS",
|
||||
"estado": "GO",
|
||||
"cep": "68907-460"
|
||||
},
|
||||
"nfe": { "ativo": True },
|
||||
"nfse": { "ativo": False }
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
env = validate_payload(payload)
|
||||
print("VALIDO")
|
||||
except Exception as e:
|
||||
print("ERRO:", type(e))
|
||||
print(e)
|
||||
86
buildImages.sh
Normal file
86
buildImages.sh
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# GERA AS IMAGENS QUE SERAO PUBLICADAS NO HUB DOCKER
|
||||
#
|
||||
# TODOS OS PROJETOS COMPARTILHAM DA MAJOR VERSION DA IMAGEM
|
||||
# EMBORA INTERNAMENTE TENHA MINOR E PATCH VERSION DIFERENTES
|
||||
#
|
||||
# FORMATO MAJOR.MINOR.PATCH
|
||||
#
|
||||
# A IMAGEM LISTA APENAS A MAJOR VERSION COMEÇANDO DA v1
|
||||
# OPCOES DE EXECUÇÃO - git clone prune
|
||||
version=':v1' # Versão de Inicial
|
||||
# BUILD IMAGE 1/7/2025 - Caso não aja o diretorio utils ou Models clone é compulsorio
|
||||
# BUILD IMAGE 1/7/2025 - Identifica o comando se docker compose ou docker-compose
|
||||
# BUILD IMAGE 1/7/2025 - Permite mudar a configuração do Dockerfile com argumentos
|
||||
|
||||
buildarg='INSTALL_GCC=false'
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
cd ..
|
||||
|
||||
}
|
||||
|
||||
for arg in "$@"; do
|
||||
if [ -n "$arg" ] && [ "$arg" == "git" ]; then
|
||||
echo "VERSAO ATUAL SERVICO_TECNOSPEED 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 SERVICO_TECNOSPEED ${version}"
|
||||
$DOCKER_COMPOSE_CMD down
|
||||
docker image ls -q -f "reference=servico_tecnospeed-*${version}" | xargs -I {} docker image rm -f {}
|
||||
docker system prune -f
|
||||
fi
|
||||
done
|
||||
|
||||
echo "GERAR IMAGENS SERVICO_TECNOSPEED ${version}"
|
||||
|
||||
echo "SERVICO_TECNOSPEED SERVICE"
|
||||
cd src
|
||||
cp ../utils/Exchange2.py ./utils
|
||||
cp ../Model/sg_tecnospeed_models/*.py ./Models
|
||||
echo servico_tecnospeed${version}
|
||||
cd ..
|
||||
docker build --build-arg ${buildarg} -t sg_servico_tecnospeed${version} .
|
||||
rm -rf ./utils
|
||||
rm -rf ./Model
|
||||
111
docker-compose.yml
Normal file
111
docker-compose.yml
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
services:
|
||||
redis:
|
||||
image: redis
|
||||
command: redis-server --save 300 1
|
||||
restart: always
|
||||
volumes:
|
||||
- ./redis-data:/data
|
||||
|
||||
banco:
|
||||
image: postgres:15
|
||||
command: postgres -c listen_addresses='*'
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-tecnospeed}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-tecnospeed}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-tecnospeed}
|
||||
volumes:
|
||||
- ./postgres-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "15432:5432"
|
||||
|
||||
tecnospeed_rpc:
|
||||
build: ./tecnospeed_rpc
|
||||
image: servico_plugnotas-tecnospeed_rpc:v2
|
||||
command: python tecnospeed_rpc.py
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
- "TZ=America/Sao_Paulo"
|
||||
- "REDIS_HOST=redis"
|
||||
- "PLUGNOTAS_URL=https://api.sandbox.plugnotas.com.br"
|
||||
- "LISTENER_INTERNAL_URL=http://endpoint_listener:8000/listenerPlugnotas"
|
||||
volumes:
|
||||
- ./tecnospeed_rpc/src:/app
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "100m"
|
||||
max-file: "3"
|
||||
shm_size: "500m"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1"
|
||||
memory: "500m"
|
||||
depends_on:
|
||||
- redis
|
||||
- banco
|
||||
|
||||
tecnospeed_queue:
|
||||
build: ./tecnospeed_queue
|
||||
image: servico_plugnotas-tecnospeed_queue:v2
|
||||
command: python tecnospeed_queue.py
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
- "TZ=America/Sao_Paulo"
|
||||
- "REDIS_HOST=redis"
|
||||
- "PLUGNOTAS_URL=https://api.sandbox.plugnotas.com.br"
|
||||
- "LISTENER_INTERNAL_URL=http://endpoint_listener:8000/listenerPlugnotas"
|
||||
volumes:
|
||||
- ./tecnospeed_queue/src:/app
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "100m"
|
||||
max-file: "3"
|
||||
shm_size: "500m"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1"
|
||||
memory: "500m"
|
||||
networks:
|
||||
- default
|
||||
depends_on:
|
||||
- redis
|
||||
- banco
|
||||
|
||||
endpoint_listener:
|
||||
build: ./endpoint_listener
|
||||
image: servico_plugnotas-endpoint_listener:v2
|
||||
command: python endpoint_listener.py
|
||||
restart: always
|
||||
environment:
|
||||
- "TZ=America/Sao_Paulo"
|
||||
- "REDIS_HOST=redis"
|
||||
- "WEBHOOK_PORT=8000"
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./endpoint_listener/src:/app
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10k"
|
||||
max-file: "3"
|
||||
depends_on:
|
||||
- redis
|
||||
shm_size: "200m"
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: servico_plugnotas_default
|
||||
28
endpoint_listener/Dockerfile
Normal file
28
endpoint_listener/Dockerfile
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
FROM python:3.12-slim
|
||||
ARG INSTALL_GCC
|
||||
|
||||
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 \
|
||||
&& if [ "$INSTALL_GCC" = "true" ]; then \
|
||||
apt-get install -y --no-install-recommends build-essential curl; \
|
||||
fi \
|
||||
&& apt-get clean && apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN pip install --no-cache-dir unidecode
|
||||
|
||||
# PARTE ESPECIFICA DA IMAGEM
|
||||
WORKDIR /app
|
||||
RUN mkdir /app/logs
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY src .
|
||||
|
||||
CMD [ "python", "endpoint_listener.py"]
|
||||
13
endpoint_listener/requirements.txt
Normal file
13
endpoint_listener/requirements.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
sqlalchemy
|
||||
pydantic
|
||||
requests
|
||||
datetime
|
||||
redis
|
||||
pytz
|
||||
pika
|
||||
cryptography
|
||||
psycopg2-binary
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
pytest
|
||||
pytest-cov
|
||||
18
endpoint_listener/src/Models/config.py
Normal file
18
endpoint_listener/src/Models/config.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
# Dados de conexão ao banco de dados isolado do cliente e da API PlugNotas
|
||||
class Config(BaseModel):
|
||||
api_key: str | None = None
|
||||
x_api_key: str | None = None
|
||||
user_postgres: str
|
||||
pass_postgres: str
|
||||
host_postgres: str
|
||||
port_postgres: str
|
||||
database_postgres: str
|
||||
|
||||
# Informações de mensageria para a troca de arquivos (AMQP) via subagent
|
||||
class SystemConfig(BaseModel):
|
||||
serviceid: str
|
||||
servicekey: str
|
||||
origin: str
|
||||
amqps: str
|
||||
97
endpoint_listener/src/Models/empresa.py
Normal file
97
endpoint_listener/src/Models/empresa.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
###################
|
||||
## POST /empresas #
|
||||
###################
|
||||
class Endereco(BaseModel):
|
||||
codigoPais: str
|
||||
descricaoPais: str
|
||||
tipoLogradouro: str | None = None
|
||||
logradouro: str
|
||||
numero: str
|
||||
complemento: str | None = None
|
||||
tipoBairro: str | None = None
|
||||
bairro: str
|
||||
codigoCidade: str
|
||||
descricaoCidade: str
|
||||
estado: str
|
||||
cep: str
|
||||
|
||||
class Rps(BaseModel):
|
||||
lote: int
|
||||
numeracao: list
|
||||
|
||||
class Email(BaseModel):
|
||||
envio: bool
|
||||
|
||||
class Config(BaseModel):
|
||||
rps: Rps | None = None
|
||||
email: Email | None = None
|
||||
producao: bool | None = None
|
||||
envioLimitado: bool | None = None
|
||||
integracoes: list | None = None
|
||||
prefeitura: dict | str | int | None = None
|
||||
|
||||
class Nfse(BaseModel):
|
||||
config: Config | None = None
|
||||
ativo: bool = False
|
||||
tipoContrato: int | None = 0
|
||||
|
||||
class Dfe(BaseModel):
|
||||
primeiroNsu: bool
|
||||
|
||||
class ConfigNfe(BaseModel):
|
||||
dfe: Dfe | None = None
|
||||
email: Email | None = None
|
||||
integracoes: dict | None = None
|
||||
producao: bool | None = None
|
||||
impressaoFcp: bool | None = None
|
||||
impressaoPartilha: bool | None = None
|
||||
versaoManual: str | None = None
|
||||
versaoEsquema: str | None = None
|
||||
numeracao: list | None = None
|
||||
calculoAutomaticoIbpt: bool | None = True
|
||||
|
||||
class Nfe(BaseModel):
|
||||
config: ConfigNfe | None = None
|
||||
ativo: bool = False
|
||||
tipoContrato: int | None = 0
|
||||
|
||||
class ConfigNfce(BaseModel):
|
||||
email: Email | None = None
|
||||
integracoes: dict | None = None
|
||||
producao: bool | None = None
|
||||
versaoManual: str | None = None
|
||||
versaoEsquema: str | None = None
|
||||
numeracao: list | None = None
|
||||
|
||||
class Nfce(BaseModel):
|
||||
config: ConfigNfce | None = None
|
||||
ativo: bool = False
|
||||
tipoContrato: int | None = 0
|
||||
|
||||
|
||||
# Entidade Empresa que cadastra a credencial emissora no sistema da PlugNotas
|
||||
class Empresa(BaseModel):
|
||||
endereco: Endereco
|
||||
nfse: Nfse | None = None
|
||||
nfe: Nfe | None = None
|
||||
nfce: Nfce | None = None
|
||||
incentivoFiscal: bool
|
||||
incentivadorCultural: bool
|
||||
cpfCnpj: str
|
||||
inscricaoMunicipal: str
|
||||
inscricaoEstadual: str | None = None
|
||||
certificado: str
|
||||
razaoSocial: str
|
||||
nomeFantasia: str | None = None
|
||||
simplesNacional: bool
|
||||
regimeTributario: int
|
||||
regimeTributarioEspecial: int
|
||||
id: str | None = None
|
||||
banco: str | None = None
|
||||
email: str | None = None
|
||||
id_empresa: int | None = None
|
||||
|
||||
|
||||
115
endpoint_listener/src/Models/nfe.py
Normal file
115
endpoint_listener/src/Models/nfe.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
from pydantic import BaseModel
|
||||
from typing import List, Optional, Literal
|
||||
|
||||
EstadoEnum = Literal[
|
||||
"AC", "AL", "AM", "AP", "BA", "CE", "DF", "ES", "GO", "MA", "MG", "MS", "MT",
|
||||
"PA", "PB", "PE", "PI", "PR", "RJ", "RN", "RO", "RR", "RS", "SC", "SE", "SP", "TO"
|
||||
]
|
||||
|
||||
TipoLogradouroEnum = Literal[
|
||||
"Alameda", "Avenida", "Chácara", "Colônia", "Condomínio", "Estância", "Estrada",
|
||||
"Fazenda", "Praça", "Prolongamento", "Rodovia", "Rua", "Sítio", "Travessa", "Vicinal", "Eqnp"
|
||||
]
|
||||
|
||||
class Emitente(BaseModel):
|
||||
cpfCnpj: str
|
||||
inscricaoEstadual: Optional[str] = None
|
||||
|
||||
class Endereco(BaseModel):
|
||||
logradouro: str
|
||||
numero: str
|
||||
bairro: str
|
||||
codigoCidade: str
|
||||
estado: EstadoEnum
|
||||
cep: str
|
||||
tipoLogradouro: TipoLogradouroEnum
|
||||
codigoPais: Optional[str] = "1058"
|
||||
complemento: Optional[str] = None
|
||||
descricaoCidade: Optional[str] = None
|
||||
descricaoPais: Optional[str] = "Brasil"
|
||||
|
||||
class Destinatario(BaseModel):
|
||||
cpfCnpj: str
|
||||
razaoSocial: str
|
||||
nomeFantasia: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
inscricaoEstadual: Optional[str] = None
|
||||
inscricaoMunicipal: Optional[str] = None
|
||||
inscricaoSuframa: Optional[str] = None
|
||||
orgaoPublico: Optional[bool] = False
|
||||
endereco: Endereco
|
||||
|
||||
class ValorTributavel(BaseModel):
|
||||
comercial: float
|
||||
tributavel: float
|
||||
|
||||
class BaseCalculoIcms(BaseModel):
|
||||
modalidadeDeterminacao: int
|
||||
valor: float
|
||||
|
||||
class Icms(BaseModel):
|
||||
origem: str
|
||||
cst: str
|
||||
baseCalculo: BaseCalculoIcms
|
||||
aliquota: float
|
||||
valor: float
|
||||
|
||||
class BaseCalculoPis(BaseModel):
|
||||
valor: float
|
||||
quantidade: int
|
||||
|
||||
class Pis(BaseModel):
|
||||
cst: str
|
||||
baseCalculo: BaseCalculoPis
|
||||
aliquota: float
|
||||
valor: float
|
||||
|
||||
class BaseCalculoCofins(BaseModel):
|
||||
valor: float
|
||||
|
||||
class Cofins(BaseModel):
|
||||
cst: str
|
||||
baseCalculo: BaseCalculoCofins
|
||||
aliquota: float
|
||||
valor: float
|
||||
|
||||
class Tributos(BaseModel):
|
||||
icms: Optional[Icms] = None
|
||||
pis: Optional[Pis] = None
|
||||
cofins: Optional[Cofins] = None
|
||||
|
||||
class ItemNFe(BaseModel):
|
||||
codigo: str
|
||||
descricao: str
|
||||
ncm: str
|
||||
cest: Optional[str] = None
|
||||
cfop: str
|
||||
valorUnitario: ValorTributavel
|
||||
valor: float
|
||||
tributos: Tributos
|
||||
|
||||
class Pagamento(BaseModel):
|
||||
aVista: Optional[bool] = True
|
||||
meio: Optional[str] = "01"
|
||||
valor: float
|
||||
|
||||
class Telefone(BaseModel):
|
||||
ddd: str
|
||||
numero: str
|
||||
|
||||
class ResponsavelTecnico(BaseModel):
|
||||
cpfCnpj: str
|
||||
nome: str
|
||||
email: str
|
||||
telefone: Telefone
|
||||
|
||||
class NFe(BaseModel):
|
||||
idIntegracao: str
|
||||
presencial: bool
|
||||
consumidorFinal: bool
|
||||
natureza: str
|
||||
emitente: Emitente
|
||||
destinatario: Destinatario
|
||||
itens: List[ItemNFe]
|
||||
pagamentos: List[Pagamento]
|
||||
responsavelTecnico: Optional[ResponsavelTecnico] = None
|
||||
49
endpoint_listener/src/Models/nfse.py
Normal file
49
endpoint_listener/src/Models/nfse.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
class Prestador(BaseModel):
|
||||
cpfCnpj: str
|
||||
|
||||
class Endereco(BaseModel):
|
||||
descricaoCidade: str
|
||||
cep: str
|
||||
tipoLogradouro: str
|
||||
logradouro: str
|
||||
tipoBairro: Optional[str] = None
|
||||
codigoCidade: str
|
||||
complemento: Optional[str] = None
|
||||
estado: str
|
||||
numero: str
|
||||
bairro: str
|
||||
|
||||
class Tomador(BaseModel):
|
||||
cpfCnpj: str
|
||||
razaoSocial: str
|
||||
inscricaoMunicipal: Optional[str] = None
|
||||
email: str
|
||||
endereco: dict
|
||||
|
||||
class ISS(BaseModel):
|
||||
tipoTributacao: int
|
||||
exigibilidade: int
|
||||
aliquota: float
|
||||
|
||||
class Valor(BaseModel):
|
||||
servico: float
|
||||
descontoCondicionado: Optional[float] = 0
|
||||
descontoIncondicionado: Optional[float] = 0
|
||||
|
||||
class Servico(BaseModel):
|
||||
codigo: str
|
||||
codigoTributacao: str
|
||||
discriminacao: str
|
||||
cnae: str
|
||||
iss: ISS
|
||||
valor: Valor
|
||||
|
||||
# Estrutura padrão de envio de uma NFSe contendo Prestador, Tomador e Serviço prestado
|
||||
class NFSe(BaseModel):
|
||||
idIntegracao: str
|
||||
prestador: Prestador
|
||||
tomador: Tomador
|
||||
servico: Servico
|
||||
518
endpoint_listener/src/Models/payload.py
Normal file
518
endpoint_listener/src/Models/payload.py
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
from enum import Enum
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from Models.config import Config
|
||||
from Models.empresa import Empresa
|
||||
from Models.nfe import NFe
|
||||
from Models.nfse import NFSe
|
||||
from Models.servico import Servico
|
||||
from Models.tomador import Tomador
|
||||
|
||||
|
||||
class Fila(str, Enum):
|
||||
NFE = "nfe"
|
||||
NFSE = "nfse"
|
||||
CERTIFICADO_DIGITAL = "certificado_digital"
|
||||
EMPRESA = "empresa"
|
||||
SERVICO = "servico"
|
||||
TOMADOR = "tomador"
|
||||
RELATORIOS = "relatorios"
|
||||
WEBHOOKS = "webhooks"
|
||||
|
||||
|
||||
class NFeMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
DOWNLOAD = "download"
|
||||
DOWNLOAD_XML = "download_xml"
|
||||
CANCELAR = "cancelar"
|
||||
STATUS_CANCELAMENTO = "status_cancelamento"
|
||||
CCE = "cce"
|
||||
STATUS_CCE = "status_cce"
|
||||
RESUMO = "resumo"
|
||||
|
||||
|
||||
class NFSeMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
DOWNLOAD = "download"
|
||||
DOWNLOAD_XML = "download_xml"
|
||||
CANCELAR = "cancelar"
|
||||
STATUS_CANCELAMENTO = "status_cancelamento"
|
||||
RESUMO = "resumo"
|
||||
HISTORICO = "historico"
|
||||
|
||||
|
||||
class CertificadoMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
PUT = "put"
|
||||
|
||||
|
||||
class EmpresaMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
POST_FILE = "post_file"
|
||||
PATCH = "patch"
|
||||
|
||||
|
||||
class ServicoMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
PUT = "put"
|
||||
DELETE = "delete"
|
||||
|
||||
|
||||
class TomadorMetodo(str, Enum):
|
||||
POST = "post"
|
||||
GET = "get"
|
||||
PUT = "put"
|
||||
DELETE = "delete"
|
||||
|
||||
|
||||
class RelatorioMetodo(str, Enum):
|
||||
NFE_EMITIDAS = "nfe_emitidas"
|
||||
NFE_POR_CNPJ = "nfe_por_cnpj"
|
||||
NFSE_EMITIDAS = "nfse_emitidas"
|
||||
NFSE_POR_CNPJ = "nfse_por_cnpj"
|
||||
|
||||
|
||||
class WebhookMetodo(str, Enum):
|
||||
GET = "get"
|
||||
POST = "post"
|
||||
DELETE = "delete"
|
||||
PUT = "put"
|
||||
TEST = "test"
|
||||
|
||||
|
||||
class BaseEnvelope(BaseModel):
|
||||
cliente: str | None = None
|
||||
config: Config
|
||||
url_base: str | None = None
|
||||
|
||||
|
||||
# --- Base Data Models ---
|
||||
|
||||
class BaseDados(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class NFePostDadosWrapped(BaseDados):
|
||||
nfe: NFe
|
||||
id_empresa: str | None = None
|
||||
|
||||
|
||||
class NFePostDadosDirect(NFe):
|
||||
id_usuario: int | None = 1
|
||||
id_empresa: str | None = None
|
||||
|
||||
|
||||
class NFeIdDados(BaseDados):
|
||||
id: str
|
||||
|
||||
|
||||
class NFeCancelarDados(NFeIdDados):
|
||||
justificativa: str | None = None
|
||||
|
||||
|
||||
class NFeCCEDados(NFeIdDados):
|
||||
correcao: str
|
||||
|
||||
|
||||
class NFeResumoDados(BaseDados):
|
||||
cnpj: str
|
||||
id_integracao: str | None = None
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class NFSePostDados(NFSe):
|
||||
id_usuario: int | None = 1
|
||||
id_empresa: str | None = None
|
||||
|
||||
|
||||
class NFSeIdDados(BaseDados):
|
||||
id: str
|
||||
|
||||
|
||||
class NFSeGetDados(BaseDados):
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class NFSeResumoDados(NFSeIdDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class NFSeHistoricoDados(NFSeIdDados):
|
||||
cnpj: str | None = None
|
||||
|
||||
|
||||
class CertificadoPostDados(BaseDados):
|
||||
arquivo: str
|
||||
senha: str
|
||||
id_empresa: str | None = None
|
||||
email: str | None = None
|
||||
expiration_at: str | None = Field(None, alias="expirationAt")
|
||||
data_expiracao: str | None = None
|
||||
|
||||
|
||||
class CertificadoGetDados(BaseDados):
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class CertificadoPutDados(CertificadoPostDados):
|
||||
id: str
|
||||
|
||||
|
||||
class EmpresaPostDados(Empresa):
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class EmpresaGetDados(BaseDados):
|
||||
cnpj: str | None = None
|
||||
|
||||
|
||||
class EmpresaPostFileDados(BaseDados):
|
||||
file: str
|
||||
cnpj: str
|
||||
|
||||
|
||||
class EmpresaPatchDados(BaseDados):
|
||||
cnpj: str
|
||||
certificado: str
|
||||
|
||||
|
||||
class ServicoPostDados(Servico):
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class ServicoGetDados(BaseDados):
|
||||
id: str | None = None
|
||||
|
||||
|
||||
class ServicoPutDados(Servico):
|
||||
id: str
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class ServicoDeleteDados(BaseDados):
|
||||
id: str
|
||||
|
||||
|
||||
class TomadorPostDados(Tomador):
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class TomadorGetDados(BaseDados):
|
||||
cpf_cnpj: str | None = None
|
||||
|
||||
|
||||
class TomadorPutDados(Tomador):
|
||||
cpf_cnpj: str
|
||||
id_usuario: int | None = 1
|
||||
|
||||
|
||||
class TomadorDeleteDados(BaseDados):
|
||||
cpf_cnpj: str
|
||||
|
||||
|
||||
class RelatorioVazioDados(BaseDados):
|
||||
pass
|
||||
|
||||
|
||||
class RelatorioCnpjDados(BaseDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class RelatorioPeriodoDados(BaseDados):
|
||||
data_inicial: str | None = None
|
||||
data_final: str | None = None
|
||||
|
||||
|
||||
class RelatorioCnpjPeriodoDados(RelatorioPeriodoDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class WebhookGetDeleteTestDados(BaseDados):
|
||||
cnpj: str
|
||||
|
||||
|
||||
class WebhookPostPutDados(WebhookGetDeleteTestDados):
|
||||
url: str
|
||||
|
||||
|
||||
# --- Envelopes ---
|
||||
|
||||
class NFePostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.POST
|
||||
dados: NFePostDadosDirect | NFePostDadosWrapped
|
||||
|
||||
|
||||
class NFeIdEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo
|
||||
dados: NFeIdDados
|
||||
|
||||
|
||||
class NFeCancelarEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.CANCELAR
|
||||
dados: NFeCancelarDados
|
||||
|
||||
|
||||
class NFeCCEEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.CCE
|
||||
dados: NFeCCEDados
|
||||
|
||||
|
||||
class NFeResumoEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFE
|
||||
metodo: NFeMetodo = NFeMetodo.RESUMO
|
||||
dados: NFeResumoDados
|
||||
|
||||
|
||||
class NFSePostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.POST
|
||||
dados: NFSePostDados
|
||||
|
||||
|
||||
class NFSeGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.GET
|
||||
dados: NFSeGetDados
|
||||
|
||||
|
||||
class NFSeIdEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo
|
||||
dados: NFSeIdDados
|
||||
|
||||
|
||||
class NFSeResumoEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.RESUMO
|
||||
dados: NFSeResumoDados
|
||||
|
||||
|
||||
class NFSeHistoricoEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.NFSE
|
||||
metodo: NFSeMetodo = NFSeMetodo.HISTORICO
|
||||
dados: NFSeHistoricoDados
|
||||
|
||||
|
||||
class CertificadoPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.CERTIFICADO_DIGITAL
|
||||
metodo: CertificadoMetodo = CertificadoMetodo.POST
|
||||
dados: CertificadoPostDados
|
||||
|
||||
|
||||
class CertificadoGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.CERTIFICADO_DIGITAL
|
||||
metodo: CertificadoMetodo = CertificadoMetodo.GET
|
||||
dados: CertificadoGetDados
|
||||
|
||||
|
||||
class CertificadoPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.CERTIFICADO_DIGITAL
|
||||
metodo: CertificadoMetodo = CertificadoMetodo.PUT
|
||||
dados: CertificadoPutDados
|
||||
|
||||
|
||||
class EmpresaPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.POST
|
||||
dados: EmpresaPostDados
|
||||
|
||||
|
||||
class EmpresaGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.GET
|
||||
dados: EmpresaGetDados
|
||||
|
||||
|
||||
class EmpresaPostFileEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.POST_FILE
|
||||
dados: EmpresaPostFileDados
|
||||
|
||||
|
||||
class EmpresaPatchEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.EMPRESA
|
||||
metodo: EmpresaMetodo = EmpresaMetodo.PATCH
|
||||
dados: EmpresaPatchDados
|
||||
|
||||
|
||||
class ServicoPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.POST
|
||||
dados: ServicoPostDados
|
||||
|
||||
|
||||
class ServicoGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.GET
|
||||
dados: ServicoGetDados
|
||||
|
||||
|
||||
class ServicoPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.PUT
|
||||
dados: ServicoPutDados
|
||||
|
||||
|
||||
class ServicoDeleteEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.SERVICO
|
||||
metodo: ServicoMetodo = ServicoMetodo.DELETE
|
||||
dados: ServicoDeleteDados
|
||||
|
||||
|
||||
class TomadorPostEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.POST
|
||||
dados: TomadorPostDados
|
||||
|
||||
|
||||
class TomadorGetEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.GET
|
||||
dados: TomadorGetDados
|
||||
|
||||
|
||||
class TomadorPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.PUT
|
||||
dados: TomadorPutDados
|
||||
|
||||
|
||||
class TomadorDeleteEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.TOMADOR
|
||||
metodo: TomadorMetodo = TomadorMetodo.DELETE
|
||||
dados: TomadorDeleteDados
|
||||
|
||||
|
||||
class RelatorioNFeEmitidasEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFE_EMITIDAS
|
||||
dados: RelatorioVazioDados
|
||||
|
||||
|
||||
class RelatorioNFeCnpjEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFE_POR_CNPJ
|
||||
dados: RelatorioCnpjDados
|
||||
|
||||
|
||||
class RelatorioNFSeEmitidasEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFSE_EMITIDAS
|
||||
dados: RelatorioPeriodoDados
|
||||
|
||||
|
||||
class RelatorioNFSeCnpjEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.RELATORIOS
|
||||
metodo: RelatorioMetodo = RelatorioMetodo.NFSE_POR_CNPJ
|
||||
dados: RelatorioCnpjPeriodoDados
|
||||
|
||||
|
||||
class WebhookGetDeleteTestEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.WEBHOOKS
|
||||
metodo: WebhookMetodo
|
||||
dados: WebhookGetDeleteTestDados
|
||||
|
||||
|
||||
class WebhookPostPutEnvelope(BaseEnvelope):
|
||||
fila: Fila = Fila.WEBHOOKS
|
||||
metodo: WebhookMetodo
|
||||
dados: WebhookPostPutDados
|
||||
|
||||
|
||||
# --- Generic Envelope for basic structural validation ---
|
||||
|
||||
class GenericEnvelope(BaseEnvelope):
|
||||
"""Validates that minimum required fields exist before routing."""
|
||||
fila: Fila
|
||||
metodo: str
|
||||
dados: dict | None = None
|
||||
|
||||
|
||||
# --- Payload Validation Router ---
|
||||
|
||||
# Maps (fila, metodo) -> Envelope class
|
||||
_ENVELOPE_MAP: dict[tuple[str, str], type[BaseEnvelope]] = {
|
||||
# NFe
|
||||
("nfe", "post"): NFePostEnvelope,
|
||||
("nfe", "get"): NFeIdEnvelope,
|
||||
("nfe", "download"): NFeIdEnvelope,
|
||||
("nfe", "download_xml"): NFeIdEnvelope,
|
||||
("nfe", "cancelar"): NFeCancelarEnvelope,
|
||||
("nfe", "status_cancelamento"): NFeIdEnvelope,
|
||||
("nfe", "cce"): NFeCCEEnvelope,
|
||||
("nfe", "status_cce"): NFeIdEnvelope,
|
||||
("nfe", "resumo"): NFeResumoEnvelope,
|
||||
# NFSe
|
||||
("nfse", "post"): NFSePostEnvelope,
|
||||
("nfse", "get"): NFSeGetEnvelope,
|
||||
("nfse", "download"): NFSeIdEnvelope,
|
||||
("nfse", "download_xml"): NFSeIdEnvelope,
|
||||
("nfse", "cancelar"): NFSeIdEnvelope,
|
||||
("nfse", "status_cancelamento"): NFSeIdEnvelope,
|
||||
("nfse", "resumo"): NFSeResumoEnvelope,
|
||||
("nfse", "historico"): NFSeHistoricoEnvelope,
|
||||
# Certificado Digital
|
||||
("certificado_digital", "post"): CertificadoPostEnvelope,
|
||||
("certificado_digital", "get"): CertificadoGetEnvelope,
|
||||
("certificado_digital", "put"): CertificadoPutEnvelope,
|
||||
# Empresa
|
||||
("empresa", "post"): EmpresaPostEnvelope,
|
||||
("empresa", "get"): EmpresaGetEnvelope,
|
||||
("empresa", "post_file"): EmpresaPostFileEnvelope,
|
||||
("empresa", "patch"): EmpresaPatchEnvelope,
|
||||
# Serviço
|
||||
("servico", "post"): ServicoPostEnvelope,
|
||||
("servico", "get"): ServicoGetEnvelope,
|
||||
("servico", "put"): ServicoPutEnvelope,
|
||||
("servico", "delete"): ServicoDeleteEnvelope,
|
||||
# Tomador
|
||||
("tomador", "post"): TomadorPostEnvelope,
|
||||
("tomador", "get"): TomadorGetEnvelope,
|
||||
("tomador", "put"): TomadorPutEnvelope,
|
||||
("tomador", "delete"): TomadorDeleteEnvelope,
|
||||
# Relatórios
|
||||
("relatorios", "nfe_emitidas"): RelatorioNFeEmitidasEnvelope,
|
||||
("relatorios", "nfe_por_cnpj"): RelatorioNFeCnpjEnvelope,
|
||||
("relatorios", "nfse_emitidas"): RelatorioNFSeEmitidasEnvelope,
|
||||
("relatorios", "nfse_por_cnpj"): RelatorioNFSeCnpjEnvelope,
|
||||
# Webhooks
|
||||
("webhooks", "get"): WebhookGetDeleteTestEnvelope,
|
||||
("webhooks", "post"): WebhookPostPutEnvelope,
|
||||
("webhooks", "delete"): WebhookGetDeleteTestEnvelope,
|
||||
("webhooks", "put"): WebhookPostPutEnvelope,
|
||||
("webhooks", "test"): WebhookGetDeleteTestEnvelope,
|
||||
}
|
||||
|
||||
|
||||
def validate_payload(payload_dict: dict) -> BaseEnvelope:
|
||||
# Step 1: Basic structural validation
|
||||
generic = GenericEnvelope(**payload_dict)
|
||||
|
||||
fila = generic.fila.value
|
||||
metodo = generic.metodo.lower()
|
||||
|
||||
# Step 2: Find specific envelope
|
||||
key = (fila, metodo)
|
||||
envelope_cls = _ENVELOPE_MAP.get(key)
|
||||
|
||||
if envelope_cls is None:
|
||||
raise ValueError(f"Combinação fila/metodo desconhecida: {fila}/{metodo}")
|
||||
|
||||
# Step 3: Normalize metodo in the dict to match enum lowercase values
|
||||
payload_dict['metodo'] = metodo
|
||||
|
||||
# Step 4: Full typed validation
|
||||
return envelope_cls(**payload_dict)
|
||||
|
||||
|
||||
|
||||
150
endpoint_listener/src/Models/servico.py
Normal file
150
endpoint_listener/src/Models/servico.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
from pydantic import BaseModel
|
||||
from enum import IntEnum
|
||||
|
||||
class TipoTributacao(IntEnum):
|
||||
NAO_DEFINIDO = 0
|
||||
ISENTO_ISS = 1
|
||||
IMUNE = 2
|
||||
NAO_INCIDENCIA = 3
|
||||
NAO_TRIBUTAVEL = 4
|
||||
RETIDO = 5
|
||||
TRIBUTAVEL_MUNICIPIO = 6
|
||||
TRIBUTAVEL_FORA_MUNICIPIO = 7
|
||||
TRIBUTAVEL_MUNICIPIO_TOMADOR = 8
|
||||
|
||||
class TipoExigibilidade(IntEnum):
|
||||
EXIGIVEL = 1
|
||||
NAO_INCIDENCIA = 2
|
||||
ISENCAO = 3
|
||||
EXPORTACAO = 4
|
||||
IMUNIDADE = 5
|
||||
SUSPENSO_ACAO_JUDICIAL = 6
|
||||
SUSPENSO_ACAO_ADMINISTRATIVA = 7
|
||||
|
||||
class ISS(BaseModel):
|
||||
tipoTributacao: int #TipoTributacao
|
||||
exigibilidade: int #TipoExigibilidade
|
||||
retido: bool | None = None # Se o ISS foi retido na fonte
|
||||
aliquota: float # Aliquota do ISS
|
||||
valor: float # Valor do ISS
|
||||
valorRetido: float # Valor do ISS retido
|
||||
processoSuspensao: str # Numero processo de suspensão do ISS
|
||||
##############################################################################
|
||||
# tipoTributacao integer
|
||||
# Default: 6
|
||||
# Enum: 0 1 2 3 4 5 6 7 8
|
||||
# Tipo de Tributação do Serviço, valores:
|
||||
# 0 - Não definido
|
||||
# 1 - Isento de ISS
|
||||
# 2 - Imune
|
||||
# 3 - Não Incidência no Município
|
||||
# 4 - Não Tributável
|
||||
# 5 - Retido
|
||||
# 6 - Tributável Dentro do Município
|
||||
# 7 - Tributável Fora do Município
|
||||
# 8 - Tributável Dentro do Município pelo tomador
|
||||
##############################################################################
|
||||
# exigibilidade integer
|
||||
# Default: 1
|
||||
# Enum: 1 2 3 4 5 6 7
|
||||
# Exigibilidade do ISS, valores:
|
||||
# 1 - Exigível
|
||||
# 2 - Não Incidência
|
||||
# 3 - Isenção
|
||||
# 4 - Exportação
|
||||
# 5 - Imunidade
|
||||
# 6 - Supenso por Ação Judicial
|
||||
# 7 - Suspenso por Ação Administrativa
|
||||
##############################################################################
|
||||
# responsavelRetencao string
|
||||
# Valores aceitos:
|
||||
# 1 - Tomador
|
||||
# 2 - Intermediário
|
||||
##############################################################################
|
||||
|
||||
class Obra(BaseModel):
|
||||
art: str # Anotação de Responsabilidade Técnica (Codigo ART)
|
||||
codigo: str # Código da obra
|
||||
|
||||
class Valor(BaseModel):
|
||||
servico: float # Valor bruto do serviço prestado.
|
||||
baseCalculo: float # Valor da base de cálculo dos impostos.
|
||||
deducoes: float # Valor deduções.
|
||||
descontoCondicionado: float # Valor do desconto condicionado.
|
||||
descontoIncondicionado: float # Valor do desconto incondicionado.
|
||||
liquido: float # Valor liquido do serviço, caso você não informe esse campo, vamos calcular automáticamente.
|
||||
unitario: float # Valor unitário do serviço.
|
||||
valorAproximadoTributos: float # Valor aproximado dos tributos.
|
||||
|
||||
class Deducao(BaseModel):
|
||||
tipo: int
|
||||
descricao: str # Descrição da dedução
|
||||
##############################################################################
|
||||
# tipo integer
|
||||
# Tipo de dedução:
|
||||
# 0 - Sem Deduções
|
||||
# 1 - Materiais
|
||||
# 2 - Subempreitada de Mão de Obra
|
||||
# 3 - Serviços
|
||||
# 4 - Produção Externa
|
||||
# 5 - Alimentação e Bebidas
|
||||
# 6 - Reembolso de Despesas
|
||||
# 7 - Repasse Consorciado
|
||||
# 8 - Repasse Plano de Saúde
|
||||
# 99 - Outras Deduções
|
||||
##############################################################################
|
||||
|
||||
class RetencaoInterno(BaseModel):
|
||||
aliquota: float # Aliquota da retenção
|
||||
valor: float # Valor da retenção
|
||||
|
||||
class Retencao(BaseModel):
|
||||
pis: RetencaoInterno
|
||||
cofins: RetencaoInterno
|
||||
csll: RetencaoInterno
|
||||
inss: RetencaoInterno
|
||||
irrf: RetencaoInterno
|
||||
outrasRetencoes: float # Valor de outras retenções
|
||||
cpp: RetencaoInterno
|
||||
|
||||
class Aliquota(BaseModel):
|
||||
municipal: float # Aliquota municipal
|
||||
estadual: float # Aliquota estadual
|
||||
federal: float # Aliquota federal
|
||||
|
||||
class Detalhado(BaseModel):
|
||||
aliquota: Aliquota
|
||||
|
||||
class Simplificado(BaseModel):
|
||||
aliquota: float # Aliquota geral de impostos
|
||||
|
||||
class IBPT(BaseModel):
|
||||
simplificado: Simplificado
|
||||
detalhado: Detalhado
|
||||
|
||||
# Representa o detalhamento do serviço prestado na NFSe, impostos incidentes e deduções
|
||||
class Servico(BaseModel):
|
||||
codigo: str # Código de serviço, também conhecido como Código Item Lista Serviço, geralmente segue a LC116, podendo váriar de acordo com a prefeitura.Você pode encontrar esse dado no portal da prefeitura, em uma nota emitida, ou junto ao contador.
|
||||
idIntegracao: str # Código EXTERNO de integração do serviço. Utilizado SOMENTE quando se cadastra previamente um serviço.
|
||||
discriminacao: str # Descrição do serviço prestado
|
||||
codigoTributacao: str # Código tributação no Município
|
||||
cnae: str # O código CNAE ou, por extenso, Classificação Nacional de Atividades Econômicas, é uma forma de padronizar, em todo o território nacional, os códigos de atividades econômicas e os critérios de enquadramento usados pelos mais diversos órgãos da administração tributária do Brasil.
|
||||
codigoCidadeIncidencia: str # Código IBGE da cidade de incidência do ISS.
|
||||
descricaoCidadeIncidencia: str # Descrição da cidade de incidência do ISS.
|
||||
unidade: str # Unidade do serviço prestado.
|
||||
quantidade: int # Quantidade dos serviços prestados.
|
||||
iss: ISS
|
||||
obra: Obra
|
||||
valor: Valor
|
||||
deducao: Deducao
|
||||
retencao: Retencao
|
||||
tributavel: bool # Indica se o serviço está sujeito a tributação.
|
||||
ibpt: IBPT
|
||||
responsavelRetencao: str
|
||||
|
||||
##############################################################################
|
||||
# responsavelRetencao Valores aceitos:
|
||||
# 1 - Tomador
|
||||
# 2 - Intermediário
|
||||
# Caso o responsável pela retenção seja o próprio prestador, não gerar o campo.
|
||||
##############################################################################
|
||||
34
endpoint_listener/src/Models/tomador.py
Normal file
34
endpoint_listener/src/Models/tomador.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Endereco(BaseModel):
|
||||
tipoLogradouro: str
|
||||
logradouro: str
|
||||
numero: str
|
||||
complemento: str
|
||||
tipoBairro: str
|
||||
bairro: str
|
||||
codigoPais: str
|
||||
descricaoPais: str
|
||||
codigoCidade: str
|
||||
descricaoCidade: str
|
||||
estado: str
|
||||
cep: str
|
||||
|
||||
class Telefone(BaseModel):
|
||||
ddd: str
|
||||
numero: str
|
||||
|
||||
# Representa o cliente (Tomador do Serviço) vinculado a uma nota fiscal de serviço
|
||||
class Tomador(BaseModel):
|
||||
cpfCnpj: str
|
||||
inscricaoMunicipal: str
|
||||
inscricaoEstadual: str
|
||||
inscricaoSuframa: str
|
||||
indicadorInscricaoEstadual: int
|
||||
razaoSocial: str
|
||||
nomeFantasia: str
|
||||
endereco: Endereco
|
||||
telefone: Telefone
|
||||
email: str
|
||||
orgaoPublico: bool
|
||||
105
endpoint_listener/src/NFSe/Servico/servico.py
Normal file
105
endpoint_listener/src/NFSe/Servico/servico.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
from Models.servico import Servico
|
||||
from utils.api import Api
|
||||
from utils.banco import Banco, ServicoTable
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
import json
|
||||
|
||||
# Controller responsável pelos cadastros e busca de Serviços a serem vinculados em NFSe
|
||||
class ServicoController:
|
||||
def __init__(self, url_base, api_key, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/nfse/servico"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def cadastrar_servico(self, servico: Servico, data: dict):
|
||||
try:
|
||||
resposta = self.api.post(self.url, servico.model_dump())
|
||||
if 'error' in resposta:
|
||||
self._log(RealmType.ERRORS, f"Erro na API ao cadastrar serviço")
|
||||
return resposta
|
||||
|
||||
servico_novo = ServicoTable(
|
||||
descricao=servico.discriminacao,
|
||||
id_tecnospeed=resposta['data']['id'],
|
||||
id_usuario=data.get('id_usuario', 1),
|
||||
status=True
|
||||
)
|
||||
self.banco.add(servico_novo)
|
||||
self._log(RealmType.OPERATIONAL, f"Serviço cadastrado com sucesso - ID: {resposta['data']['id']}")
|
||||
return resposta
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro no banco ao cadastrar serviço: {e}")
|
||||
return {"error": f"Erro ao cadastrar serviço: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def buscar_servico(self, id_servico, data: dict = None):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_servico}")
|
||||
self._log(RealmType.OPERATIONAL, f"Serviço consultado - ID: {id_servico}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao buscar serviço: {e}")
|
||||
return {"error": f"Erro ao buscar serviço: {str(e)}"}
|
||||
|
||||
def listar_servicos(self, data: dict = None):
|
||||
try:
|
||||
servicos = self.banco.session.query(ServicoTable.id_tecnospeed, ServicoTable.descricao, ServicoTable.status).all()
|
||||
resultado = [{"id_tecnospeed": servico.id_tecnospeed, "descricao": servico.descricao, "status": servico.status} for servico in servicos]
|
||||
self._log(RealmType.OPERATIONAL, f"Listagem de serviços - {len(resultado)} registros")
|
||||
return resultado
|
||||
except SQLAlchemyError as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao listar serviços: {e}")
|
||||
return {"error": f"Erro ao listar serviços: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def atualizar_servico(self, id_servico, servico: Servico):
|
||||
try:
|
||||
resposta = self.api.patch(f"{self.url}/{id_servico}", servico.model_dump())
|
||||
servico_existente = self.banco.session.query(ServicoTable).filter_by(id=id_servico).first()
|
||||
|
||||
if not servico_existente:
|
||||
self._log(RealmType.ERRORS, f"Serviço não encontrado - ID: {id_servico}")
|
||||
return {"error": "Serviço não encontrado"}
|
||||
|
||||
servico_existente.descricao = servico.discriminacao
|
||||
self.banco.session.commit()
|
||||
|
||||
self._log(RealmType.OPERATIONAL, f"Serviço atualizado com sucesso - ID: {id_servico}")
|
||||
return resposta
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro ao atualizar serviço: {e}")
|
||||
return {"error": f"Erro ao atualizar serviço: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def deletar_servico(self, id_servico):
|
||||
try:
|
||||
resposta = self.api.delete(f"{self.url}/{id_servico}")
|
||||
servico_existente = self.banco.session.query(ServicoTable).filter_by(id=id_servico).first()
|
||||
|
||||
if not servico_existente:
|
||||
self._log(RealmType.ERRORS, f"Serviço não encontrado para deletar - ID: {id_servico}")
|
||||
return {"error": "Serviço não encontrado"}
|
||||
|
||||
self.banco.session.delete(servico_existente)
|
||||
self.banco.session.commit()
|
||||
|
||||
self._log(RealmType.OPERATIONAL, f"Serviço deletado com sucesso - ID: {id_servico}")
|
||||
return resposta
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro ao deletar serviço: {e}")
|
||||
return {"error": f"Erro ao deletar serviço: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
91
endpoint_listener/src/NFSe/Tomador/tomador.py
Normal file
91
endpoint_listener/src/NFSe/Tomador/tomador.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
from utils.api import Api
|
||||
from utils.banco import Banco, TomadorTable
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from Models.tomador import Tomador
|
||||
|
||||
import json
|
||||
|
||||
# Controller para gerenciamento no banco e na API dos Tomadores (clientes finais) das notas
|
||||
class TomadorController:
|
||||
def __init__(self, url_base, api_key, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/nfse/tomador"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def cadastrar_tomador(self, tomador: Tomador, data: dict):
|
||||
try:
|
||||
retorno = self.api.post(self.url, tomador.model_dump())
|
||||
if 'error' in retorno:
|
||||
self._log(RealmType.ERRORS, f"Erro na API ao cadastrar tomador")
|
||||
return retorno
|
||||
|
||||
tomador_novo = TomadorTable(
|
||||
id_tecnospeed=retorno['id'],
|
||||
nome=tomador.razaoSocial,
|
||||
cpf_cnpj=tomador.cpfCnpj,
|
||||
email=tomador.email,
|
||||
telefone=f"({tomador.telefone.ddd}) {tomador.telefone.numero}",
|
||||
id_usuario=data.get('id_usuario', 1),
|
||||
status=True
|
||||
)
|
||||
|
||||
self.banco.add(tomador_novo)
|
||||
self._log(RealmType.OPERATIONAL, f"Tomador cadastrado com sucesso - CPF/CNPJ: {tomador.cpfCnpj}")
|
||||
return retorno
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao cadastrar tomador: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def buscar_tomador(self, cpf_cnpj):
|
||||
try:
|
||||
tomador = self.api.get(f"{self.url}/{cpf_cnpj}")
|
||||
self._log(RealmType.OPERATIONAL, f"Tomador consultado - CPF/CNPJ: {cpf_cnpj}")
|
||||
return tomador
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao buscar tomador: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def atualizar_tomador(self, cpf_cnpj, tomador: Tomador):
|
||||
try:
|
||||
tomador_existente = self.banco.session.query(TomadorTable).filter_by(cpf_cnpj=cpf_cnpj).first()
|
||||
if not tomador_existente:
|
||||
self._log(RealmType.ERRORS, f"Tomador não encontrado - CPF/CNPJ: {cpf_cnpj}")
|
||||
return {"error": "Tomador não encontrado"}
|
||||
|
||||
resposta = self.api.patch(f"{self.url}/{cpf_cnpj}", tomador.model_dump())
|
||||
tomador_existente.nome = tomador.razaoSocial
|
||||
tomador_existente.email = tomador.email
|
||||
tomador_existente.telefone = f"({tomador.telefone.ddd}) {tomador.telefone.numero}"
|
||||
|
||||
self.banco.session.commit()
|
||||
self._log(RealmType.OPERATIONAL, f"Tomador atualizado com sucesso - CPF/CNPJ: {cpf_cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self.banco.session.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro ao atualizar tomador: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def deletar_tomador(self, cpf_cnpj):
|
||||
try:
|
||||
tomador_existente = self.banco.session.query(TomadorTable).filter_by(cpf_cnpj=cpf_cnpj).first()
|
||||
if not tomador_existente:
|
||||
self._log(RealmType.ERRORS, f"Tomador não encontrado para deletar - CPF/CNPJ: {cpf_cnpj}")
|
||||
return {"error": "Tomador não encontrado"}
|
||||
|
||||
resposta = self.api.delete(f"{self.url}/{cpf_cnpj}")
|
||||
self.banco.session.delete(tomador_existente)
|
||||
self.banco.session.commit()
|
||||
|
||||
self._log(RealmType.OPERATIONAL, f"Tomador deletado com sucesso - CPF/CNPJ: {cpf_cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self.banco.session.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro ao deletar tomador: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
167
endpoint_listener/src/NFSe/nfse.py
Normal file
167
endpoint_listener/src/NFSe/nfse.py
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
from Models.nfse import NFSe
|
||||
from utils.api import Api
|
||||
from utils.banco import Banco, NFSeTable
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from utils.notificador import NotificadorInterno
|
||||
|
||||
import json
|
||||
|
||||
# Serviço responsável pelo controle, emissão e cancelamento das Notas Fiscais de Serviço Eletrônicas (NFSe)
|
||||
class NFSeService:
|
||||
def __init__(self, api_key, url_base, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/nfse"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
self.notificador = NotificadorInterno(logger=logger) if logger else None
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def cadastrar_nfse(self, nfse: NFSe, data: dict):
|
||||
try:
|
||||
id_empresa = data.get('id_empresa', "0")
|
||||
resposta = self.api.post(self.url, [nfse.model_dump()])
|
||||
if 'error' in resposta:
|
||||
self._log(RealmType.ERRORS, f"Erro na API ao cadastrar NFSe: {resposta.get('error')}")
|
||||
return resposta
|
||||
|
||||
# A API da Plugnotas retorna 'documents' contendo uma lista com os sucessos
|
||||
documentos = resposta.get('documents', [])
|
||||
if not documentos:
|
||||
self._log(RealmType.ERRORS, f"Nenhum documento retornado pela API. Resposta: {resposta}")
|
||||
return resposta
|
||||
|
||||
id_recebido = documentos[0].get('id')
|
||||
if not id_recebido:
|
||||
raise Exception("ID do documento não encontrado na resposta.")
|
||||
|
||||
nfse_nova = NFSeTable(
|
||||
id_tecnospeed=id_recebido,
|
||||
id_empresa=id_empresa,
|
||||
id_usuario=data.get('id_usuario', 1),
|
||||
status=True
|
||||
)
|
||||
|
||||
self.banco.add(nfse_nova)
|
||||
self._log(RealmType.OPERATIONAL, f"NFSe cadastrada com sucesso - ID: {id_recebido}")
|
||||
|
||||
# Notifica o listener interno para sincronização imediata
|
||||
if self.notificador:
|
||||
self.notificador.notificar_emissao(id_tecnospeed=id_recebido, documento="nfse", cnpj=id_empresa)
|
||||
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao cadastrar NFSe: {e}")
|
||||
raise Exception(f"Erro ao cadastrar NFSe: {e}")
|
||||
|
||||
def buscar_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nfse}")
|
||||
self._log(RealmType.OPERATIONAL, f"NFSe consultada com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao buscar NFSe: {e}")
|
||||
raise Exception(f"Erro ao buscar NFSe: {e}")
|
||||
|
||||
def listar_nfse(self):
|
||||
try:
|
||||
nfse = self.banco.session.query(NFSeTable.id, NFSeTable.id_tecnospeed).all()
|
||||
resultado = [{"id": n[0], "descricao": n[1]} for n in nfse]
|
||||
self._log(RealmType.OPERATIONAL, f"Listagem de NFSe - {len(resultado)} registros")
|
||||
return resultado
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao listar NFSe: {e}")
|
||||
raise Exception(f"Erro ao listar NFSe: {e}")
|
||||
|
||||
def baixar_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/pdf/{id_nfse}")
|
||||
self._log(RealmType.OPERATIONAL, f"PDF NFSe baixado com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao baixar NFSe: {e}")
|
||||
raise Exception(f"Erro ao baixar NFSe: {e}")
|
||||
|
||||
def baixar_xml_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/xml/{id_nfse}")
|
||||
self._log(RealmType.OPERATIONAL, f"XML NFSe baixado com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao baixar XML NFSe: {e}")
|
||||
raise Exception(f"Erro ao baixar XML NFSe: {e}")
|
||||
|
||||
def solicitar_cancelamento_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/cancelar/{id_nfse}", {})
|
||||
self._log(RealmType.OPERATIONAL, f"Cancelamento NFSe solicitado - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao solicitar cancelamento NFSe: {e}")
|
||||
raise Exception(f"Erro ao solicitar cancelamento NFSe: {e}")
|
||||
|
||||
def consultar_status_cancelamento_nfse(self, id_nfse):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/cancelar/status/{id_nfse}")
|
||||
self._log(RealmType.OPERATIONAL, f"Status cancelamento NFSe consultado - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao consultar status cancelamento NFSe: {e}")
|
||||
raise Exception(f"Erro ao consultar status cancelamento NFSe: {e}")
|
||||
|
||||
def consultar_resumo_nfse(self, id_nfse, cnpj):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/consultar/{id_nfse}/{cnpj}")
|
||||
self._log(RealmType.OPERATIONAL, f"Resumo NFSe consultado - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao consultar resumo NFSe: {e}")
|
||||
raise Exception(f"Erro ao consultar resumo NFSe: {e}")
|
||||
|
||||
def baixar_histotico_nfse(self, id_nfse, cnpj = None):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/historico/{id_nfse}") if cnpj is None else self.api.get(f"{self.url}/historico/{id_nfse}/{cnpj}")
|
||||
self._log(RealmType.OPERATIONAL, f"Histórico NFSe baixado - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao baixar histórico NFSe: {e}")
|
||||
raise Exception(f"Erro ao baixar histórico NFSe: {e}")
|
||||
|
||||
def enviar_email_nfse(self, id_nfse, email:list[str], reenvio:bool = False):
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/email/{id_nfse}", {"reenvio": reenvio,"destinatarios": email})
|
||||
self._log(RealmType.OPERATIONAL, f"Email NFSe enviado para {email} - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao enviar email NFSe: {e}")
|
||||
raise Exception(f"Erro ao enviar email NFSe: {e}")
|
||||
|
||||
def resolver_nfse(self, id_nfse, identificacaoNota:str):
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/resolve/{id_nfse}", {"identificacaoNota": identificacaoNota})
|
||||
self._log(RealmType.OPERATIONAL, f"NFSe resolvida com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao resolver NFSe: {e}")
|
||||
raise Exception(f"Erro ao resolver NFSe: {e}")
|
||||
|
||||
def consultar_nfse_periodo(self, data_inicio, data_fim):
|
||||
try:
|
||||
resposta = self.api.get_paginated(f"{self.url}/periodo/{data_inicio}/{data_fim}")
|
||||
self._log(RealmType.OPERATIONAL, f"NFSe consultada por período: {data_inicio} a {data_fim}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao consultar NFSe por período: {e}")
|
||||
raise Exception(f"Erro ao consultar NFSe por período: {e}")
|
||||
|
||||
def baixar_recibo_provisorio_servico(self, id_nfse, cnpj = None):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/rps/pdf/{id_nfse}") if cnpj is None else self.api.get(f"{self.url}/rps/pdf/{id_nfse}/{cnpj}")
|
||||
self._log(RealmType.OPERATIONAL, f"RPS baixado com sucesso - ID: {id_nfse}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao baixar recibo provisório de serviço: {e}")
|
||||
raise Exception(f"Erro ao baixar recibo provisório de serviço: {e}")
|
||||
126
endpoint_listener/src/NFe/registroNFe.py
Normal file
126
endpoint_listener/src/NFe/registroNFe.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import json as json_lib
|
||||
from utils.api import Api
|
||||
from utils.banco import Banco, NFeTable
|
||||
from Models.nfe import NFe
|
||||
from typing import List
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from utils.notificador import NotificadorInterno
|
||||
|
||||
class RegistroNFeService:
|
||||
# Classe responsável por fornecer as operações de comunicação com a PlugNotas para o documento NFe
|
||||
def __init__(self, api_key: str, url_base: str, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/nfe"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
self.notificador = NotificadorInterno(logger=logger) if logger else None
|
||||
|
||||
def cadastrar_nfe(self, nfe_list: List[NFe], data: dict):
|
||||
# Envia uma lista de NFes em lote ou unitária para emissão e registra no banco de dados
|
||||
try:
|
||||
id_empresa = data.get('id_empresa', "0")
|
||||
payload = [nfe.model_dump() for nfe in nfe_list]
|
||||
resposta = self.api.post(self.url, payload)
|
||||
if 'error' in resposta:
|
||||
id_usr = data.get('id_usuario', 1)
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'nfe', id_usr, '0', 'ERRO', f"Erro na API: {resposta.get('error')}", json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
if 'documents' in resposta:
|
||||
for doc in resposta['documents']:
|
||||
id_tecnospeed = doc.get('id')
|
||||
if id_tecnospeed:
|
||||
nfe_nova = NFeTable(
|
||||
id_tecnospeed=id_tecnospeed,
|
||||
id_empresa=id_empresa,
|
||||
id_usuario=data.get('id_usuario', 1),
|
||||
status=True
|
||||
)
|
||||
self.banco.add(nfe_nova)
|
||||
id_usr = data.get('id_usuario', 1)
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'nfe', id_usr, str(nfe_nova.id), 'SUCESSO', 'NFe cadastrada com sucesso', json_lib.dumps(doc)))
|
||||
|
||||
# Notifica o listener interno para sincronização imediata
|
||||
if self.notificador:
|
||||
self.notificador.notificar_emissao(id_tecnospeed=id_tecnospeed, documento="nfe", cnpj=id_empresa)
|
||||
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'nfe', data.get('id_usuario', 1), '0', 'ERRO', f"Erro ao cadastrar NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao cadastrar NFe: {e}")
|
||||
|
||||
def cancelar_nfe(self, id_nota: str, justificativa: str):
|
||||
# Solicita o cancelamento de uma NFe existente mediante uma justificativa
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/{id_nota}/cancelamento", {"justificativa": justificativa})
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DELETE', 'nfe', 1, id_nota, 'SUCESSO', 'NFe cancelada com sucesso', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DELETE', 'nfe', 1, id_nota, 'ERRO', f"Erro ao cancelar NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao cancelar NFe: {e}")
|
||||
|
||||
def enviar_cce(self, id_nota: str, correcao: str):
|
||||
# Envia uma Carta de Correção Eletrônica (CCe) para retificar uma NFe já emitida
|
||||
try:
|
||||
resposta = self.api.post(f"{self.url}/{id_nota}/cce", {"correcao": correcao})
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'nfe', 1, id_nota, 'SUCESSO', 'CCe enviada com sucesso', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'nfe', 1, id_nota, 'ERRO', f"Erro ao enviar CCe NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao enviar CCe NFe: {e}")
|
||||
|
||||
def consultar_resumo(self, id_nota: str):
|
||||
# Consulta o resumo e o status atual da NFe na PlugNotas pelo seu ID
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/resumo")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', 1, id_nota, 'SUCESSO', 'Resumo de NFe consultado', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', 1, id_nota, 'ERRO', f"Erro ao consultar resumo NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao consultar resumo NFe: {e}")
|
||||
|
||||
def consultar_resumo_por_cnpj_integracao(self, cnpj: str, id_integracao: str):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{cnpj}/{id_integracao}/resumo")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_integracao, id_integracao, 'SUCESSO', 'Resumo por CNPJ/Integração consultado', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_integracao, id_integracao, 'ERRO', f"Erro ao consultar resumo NFe por CNPJ/Integração: {e}", "{}"))
|
||||
raise Exception(f"Erro ao consultar resumo NFe por CNPJ/Integração: {e}")
|
||||
|
||||
def consultar_status_cancelamento(self, id_nota: str):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/cancelamento/status")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_nota, id_nota, 'SUCESSO', 'Status de cancelamento consultado', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_nota, id_nota, 'ERRO', f"Erro ao consultar status de cancelamento NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao consultar status de cancelamento NFe: {e}")
|
||||
|
||||
def consultar_status_cce(self, id_nota: str):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/cce/status")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_nota, id_nota, 'SUCESSO', 'Status CCe consultado', json_lib.dumps(resposta)))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'nfe', id_nota, id_nota, 'ERRO', f"Erro ao consultar status CCe NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao consultar status CCe NFe: {e}")
|
||||
|
||||
def baixar_pdf(self, id_nota: str):
|
||||
# Obtém o link para download ou o stream do PDF da Nota Fiscal processada
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/pdf")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DOWNLOAD (PDF)', 'nfe', id_nota, id_nota, 'SUCESSO', 'PDF baixado com sucesso', json_lib.dumps(resposta) if isinstance(resposta, dict) else '{}'))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DOWNLOAD (PDF)', 'nfe', id_nota, id_nota, 'ERRO', f"Erro ao baixar PDF NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao baixar PDF NFe: {e}")
|
||||
|
||||
def baixar_xml(self, id_nota: str):
|
||||
try:
|
||||
resposta = self.api.get(f"{self.url}/{id_nota}/xml")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DOWNLOAD (XML)', 'nfe', id_nota, id_nota, 'SUCESSO', 'XML baixado com sucesso', json_lib.dumps(resposta) if isinstance(resposta, dict) else '{}'))
|
||||
return resposta
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('DOWNLOAD (XML)', 'nfe', id_nota, id_nota, 'ERRO', f"Erro ao baixar XML NFe: {e}", "{}"))
|
||||
raise Exception(f"Erro ao baixar XML NFe: {e}")
|
||||
70
endpoint_listener/src/Relatorios/relatorios.py
Normal file
70
endpoint_listener/src/Relatorios/relatorios.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
from utils.api import Api
|
||||
from utils.banco import Banco
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
# Extração e consumo de relatórios massivos de emissões diretamente da API e consolidações em lotes
|
||||
class Relatorios:
|
||||
def __init__(self, url_base, api_key, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key)
|
||||
self.url = f"{url_base}/"
|
||||
self.banco = banco
|
||||
self.logger = logger
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def nfe_emitidas(self, data: dict = None):
|
||||
try:
|
||||
resposta = self.api.get_paginated(f"{self.url}nfe/relatorio")
|
||||
self._log(RealmType.OPERATIONAL, "Relatório de NFe emitidas consultado")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
msg_erro = f"Erro ao consultar relatório NFe emitidas: {e}"
|
||||
self._log(RealmType.ERRORS, msg_erro)
|
||||
return {"error": msg_erro}
|
||||
|
||||
def nfe_por_cnpj(self, cnpj, data: dict = None):
|
||||
try:
|
||||
resposta = self.api.get_paginated(f"{self.url}nfe/{cnpj}/relatorio")
|
||||
self._log(RealmType.OPERATIONAL, f"Relatório de NFe por CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
msg_erro = f"Erro ao consultar relatório NFe por CNPJ: {e}"
|
||||
self._log(RealmType.ERRORS, msg_erro)
|
||||
return {"error": msg_erro}
|
||||
|
||||
def nfse_emitidas(self, data_inicial=None, data_final=None, data: dict = None):
|
||||
try:
|
||||
if not data_inicial:
|
||||
data_inicial = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
|
||||
if not data_final:
|
||||
data_final = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
resposta = self.api.get_paginated(f"{self.url}nfse/relatorio?from={data_inicial}&to={data_final}")
|
||||
self._log(RealmType.OPERATIONAL, f"Relatório NFSe emitidas: {data_inicial} a {data_final}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
msg_erro = f"Erro ao consultar relatório NFSe emitidas: {e}"
|
||||
self._log(RealmType.ERRORS, msg_erro)
|
||||
return {"error": msg_erro}
|
||||
|
||||
def nfse_por_cnpj(self, cnpj, data_inicial=None, data_final=None, data: dict = None):
|
||||
try:
|
||||
if not data_inicial:
|
||||
data_inicial = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
|
||||
if not data_final:
|
||||
data_final = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
resposta = self.api.get_paginated(f"{self.url}nfse/{cnpj}/relatorio?from={data_inicial}&to={data_final}")
|
||||
self._log(RealmType.OPERATIONAL, f"Relatório NFSe por CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
msg_erro = f"Erro ao consultar relatório NFSe por CNPJ: {e}"
|
||||
self._log(RealmType.ERRORS, msg_erro)
|
||||
return {"error": msg_erro}
|
||||
|
||||
165
endpoint_listener/src/certificado_digital/certificado_digital.py
Normal file
165
endpoint_listener/src/certificado_digital/certificado_digital.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
from utils.api import Api
|
||||
from utils.banco import Banco, CertificadoDigitalTable
|
||||
from datetime import date
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
import json
|
||||
import base64
|
||||
|
||||
class CertificadoDigital:
|
||||
# Serviço que gerencia o upload e atualização de certificados digitais A1/A3 na plataforma da PlugNotas
|
||||
def __init__(self, url_base, banco: Banco, api_key: str, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key=api_key)
|
||||
self.banco = banco
|
||||
self.url = f"{url_base}/certificado"
|
||||
self.logger = logger
|
||||
|
||||
def post_form_data(self, data):
|
||||
# Realiza o envio multipart/form-data do arquivo físico do certificado .pfx para a API e o salva no banco
|
||||
try:
|
||||
files = {
|
||||
'arquivo': (
|
||||
'certificado.pfx',
|
||||
base64.b64decode(data['arquivo']) if isinstance(data['arquivo'], str) else data['arquivo'],
|
||||
'application/x-pkcs12'
|
||||
),
|
||||
'senha': (None, data['senha'])
|
||||
}
|
||||
if 'email' in data:
|
||||
files['email'] = (None, data['email'])
|
||||
|
||||
response = self.api.post_form_data(self.url, files)
|
||||
id_tecnospeed = response.get('data', {}).get('id')
|
||||
|
||||
id_usr = data.get('id_usuario', 0)
|
||||
id_emp = data.get('id_empresa', "0")
|
||||
|
||||
if not id_tecnospeed:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'certificado_digital', id_usr, '0', 'ERRO', 'ID do certificado não encontrado na resposta da API', json.dumps(response)))
|
||||
raise ValueError("Erro ao obter ID do certificado na resposta da API.")
|
||||
|
||||
expiration_value = data.get('expirationAt') or data.get('data_expiracao')
|
||||
certificado = CertificadoDigitalTable(
|
||||
id_tecnospeed=id_tecnospeed,
|
||||
certificado=data['arquivo'],
|
||||
expirationAt=date.fromisoformat(expiration_value) if expiration_value else date.today(),
|
||||
senha=data['senha'],
|
||||
status=True,
|
||||
id_empresa=id_emp,
|
||||
id_usuario=id_usr,
|
||||
email=data.get('email')
|
||||
)
|
||||
|
||||
self.banco.add(certificado)
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'certificado_digital', id_usr, id_tecnospeed, 'SUCESSO', 'Certificado digital cadastrado com sucesso', json.dumps(response)))
|
||||
|
||||
return id_tecnospeed
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('INSERT', 'certificado_digital', data.get('id_usuario', 0), '0', 'ERRO', f"Erro ao adicionar certificado: {e}", "{}"))
|
||||
raise
|
||||
|
||||
def contar_vencidos(self, lista_certificados):
|
||||
# Auxiliar para contar certificados ativos e vencidos na listagem
|
||||
if isinstance(lista_certificados, dict) and "data" in lista_certificados:
|
||||
lista = lista_certificados["data"]
|
||||
elif isinstance(lista_certificados, list):
|
||||
lista = lista_certificados
|
||||
else:
|
||||
return {"certificados_ativos": 0, "certificados_vencidos": 0}
|
||||
|
||||
ativos = 0
|
||||
vencidos = 0
|
||||
hoje = date.today()
|
||||
|
||||
for cert in lista:
|
||||
if not isinstance(cert, dict): continue
|
||||
|
||||
# Tenta encontrar o campo de expiração (vencimento ou outros nomes conhecidos)
|
||||
exp_str = cert.get("vencimento") or cert.get("expirationAt") or cert.get("dataVencimento") or cert.get("data_expiracao")
|
||||
|
||||
if exp_str:
|
||||
try:
|
||||
# Tenta converter a string de data:
|
||||
# 1. Formato DD/MM/YYYY (do snipped do usuário)
|
||||
# 2. Formato ISO YYYY-MM-DD
|
||||
if "/" in exp_str:
|
||||
partes = exp_str.split(" ")[0].split("/")
|
||||
if len(partes) == 3:
|
||||
data_exp = date(int(partes[2]), int(partes[1]), int(partes[0]))
|
||||
else:
|
||||
data_exp = date.fromisoformat(exp_str[:10])
|
||||
|
||||
if data_exp >= hoje:
|
||||
ativos += 1
|
||||
else:
|
||||
vencidos += 1
|
||||
except:
|
||||
continue
|
||||
|
||||
return {
|
||||
"certificados_ativos": ativos,
|
||||
"certificados_vencidos": vencidos
|
||||
}
|
||||
|
||||
def get(self, id=None):
|
||||
# Consulta a situação de um certificado digital específico pelo ID ou lista todos vinculados
|
||||
try:
|
||||
if id:
|
||||
resultado = self.api.get(f"{self.url}/{id}")
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'certificado_digital', id, id, 'SUCESSO', f"Certificado consultado - ID: {id}", json.dumps(resultado)))
|
||||
return resultado
|
||||
else:
|
||||
resposta = self.api.get(self.url)
|
||||
|
||||
# Calcula totais
|
||||
totais = self.contar_vencidos(resposta)
|
||||
|
||||
# Organiza a resposta unificada
|
||||
resultado = {
|
||||
"certificados": resposta if isinstance(resposta, list) else resposta.get("data", []),
|
||||
"totais": totais
|
||||
}
|
||||
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'certificado_digital', 'GERAL', '0', 'SUCESSO', 'Listagem de certificados digitais', json.dumps(resultado)))
|
||||
return resultado
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('SELECT', 'certificado_digital', id or 'GERAL', '0', 'ERRO', f"Erro ao buscar certificado: {e}", "{}"))
|
||||
raise
|
||||
|
||||
def put_certificado(self, id, data):
|
||||
# Atualiza o arquivo físico ou a senha de um certificado já existente
|
||||
try:
|
||||
certificado = self.banco.session.query(CertificadoDigitalTable).filter_by(id_tecnospeed=id).first()
|
||||
id_usr = data.get('id_usuario', 0)
|
||||
|
||||
if not certificado:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'certificado_digital', id_usr, id, 'ERRO', f"Certificado com ID {id} não encontrado", "{}"))
|
||||
raise ValueError(f"Certificado com ID {id} não encontrado no banco.")
|
||||
|
||||
files = {
|
||||
'arquivo': (
|
||||
'certificado.pfx',
|
||||
base64.b64decode(data['arquivo']) if isinstance(data['arquivo'], str) else data['arquivo'],
|
||||
'application/x-pkcs12'
|
||||
),
|
||||
'senha': (None, data['senha'])
|
||||
}
|
||||
if 'email' in data:
|
||||
files['email'] = (None, data['email'])
|
||||
|
||||
self.api.put_form_data(f"{self.url}/{id}", files)
|
||||
|
||||
certificado.certificado = data['arquivo']
|
||||
certificado.senha = data['senha']
|
||||
expiration_value = data.get('expirationAt') or data.get('data_expiracao')
|
||||
if expiration_value:
|
||||
certificado.expirationAt = date.fromisoformat(expiration_value)
|
||||
certificado.updatedAt = date.today()
|
||||
|
||||
self.banco.update(certificado)
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'certificado_digital', id_usr, id, 'SUCESSO', 'Certificado digital atualizado com sucesso', json.dumps({"id": id})))
|
||||
|
||||
return id
|
||||
except Exception as e:
|
||||
if self.logger: self.logger.logIC2(RealmType.OPERATIONAL, ('UPDATE', 'certificado_digital', data.get('id_usuario', 0), id, 'ERRO', f"Erro ao atualizar certificado: {e}", "{}"))
|
||||
raise
|
||||
184
endpoint_listener/src/empresas/empresa.py
Normal file
184
endpoint_listener/src/empresas/empresa.py
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
from Models.empresa import Empresa
|
||||
from utils.api import Api
|
||||
from utils.banco import Banco, EnterpriseTable, LogoTable
|
||||
from utils.Exchange2 import LoggerExchange2
|
||||
from utils.exchangeModels import RealmType
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
import json
|
||||
import string
|
||||
import random
|
||||
import time
|
||||
|
||||
def generate_cuid():
|
||||
"""Gera um ID no formato aproximado de CUID (c + timestamp + random)."""
|
||||
ts = hex(int(time.time() * 1000))[2:]
|
||||
rand = ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
||||
return f"c{ts}{rand}"[:25]
|
||||
|
||||
# Gerenciador de contas de Empresas emissoras de notas e seus respectivos certificados e logos
|
||||
class EmpresaController:
|
||||
def __init__(self, url_base, api_key: str, banco: Banco, logger: LoggerExchange2 = None):
|
||||
self.api = Api(api_key=api_key)
|
||||
self.banco = banco
|
||||
self.url = f"{url_base}/empresa"
|
||||
self.logger = logger
|
||||
|
||||
def _log(self, realm: RealmType, msg: str):
|
||||
if self.logger:
|
||||
self.logger.logIC2(realm, (msg,))
|
||||
|
||||
def post(self, empresa: Empresa, data: dict):
|
||||
try:
|
||||
dict_api = empresa.model_dump(exclude={'id', 'id_empresa', 'banco', 'email'}, exclude_none=True)
|
||||
|
||||
# Injeção das configurações fixas e obrigatórias para a PlugNotas
|
||||
if dict_api.get("nfe") and dict_api["nfe"].get("ativo"):
|
||||
nfe_config_front = dict_api["nfe"].get("config") or {}
|
||||
dict_api["nfe"]["config"] = {
|
||||
"producao": False,
|
||||
"email": {"envio": False},
|
||||
"dfe": {"primeiroNsu": False},
|
||||
"impressaoFcp": False,
|
||||
"impressaoPartilha": False,
|
||||
**nfe_config_front
|
||||
}
|
||||
dict_api["nfe"]["tipoContrato"] = 0
|
||||
|
||||
if dict_api.get("nfse") and dict_api["nfse"].get("ativo"):
|
||||
nfse_config_front = dict_api["nfse"].get("config") or {}
|
||||
dict_api["nfse"]["config"] = {
|
||||
"producao": False,
|
||||
"email": {"envio": False},
|
||||
"rps": {"lote": 1, "numeracao": [{"numero": 1, "serie": "1"}]},
|
||||
"prefeitura": {"login": "12345", "senha": "123"},
|
||||
**nfse_config_front
|
||||
}
|
||||
dict_api["nfse"]["tipoContrato"] = 0
|
||||
print("===========================")
|
||||
print("ENVIANDO PARA A API:", json.dumps(dict_api, indent=2))
|
||||
print("===========================")
|
||||
retorno = self.api.post(data=dict_api, url=self.url)
|
||||
if 'error' in retorno:
|
||||
err_data = retorno.get('response', {}).get('error', {})
|
||||
msg = err_data.get('message', 'Erro desconhecido')
|
||||
detalhes = err_data.get('data', [])
|
||||
if detalhes:
|
||||
msg += f" Detalhes: {detalhes}"
|
||||
|
||||
print(f"\n[ERRO API PLUGNOTAS] {msg}\n", flush=True)
|
||||
self._log(RealmType.ERRORS, f"Erro na API ao cadastrar empresa: {msg}")
|
||||
raise Exception(msg)
|
||||
|
||||
# Gerar ID CUID se não vier no objeto ou se for nulo
|
||||
empresa_id = empresa.id if empresa.id else generate_cuid()
|
||||
|
||||
empresa_nova = EnterpriseTable(
|
||||
id=empresa_id,
|
||||
cnpj=empresa.cpfCnpj,
|
||||
razao_social=empresa.razaoSocial,
|
||||
nome_fantasia=empresa.nomeFantasia if empresa.nomeFantasia else empresa.razaoSocial,
|
||||
banco=empresa.banco,
|
||||
email=empresa.email,
|
||||
status=True
|
||||
)
|
||||
self.banco.add(empresa_nova)
|
||||
self._log(RealmType.OPERATIONAL, f"Empresa cadastrada com sucesso - CNPJ: {empresa.cpfCnpj} (ID: {empresa_id})")
|
||||
|
||||
if isinstance(retorno, dict) and 'data' in retorno:
|
||||
retorno['data']['id'] = empresa_nova.id
|
||||
retorno['data']['id_plugnotas'] = retorno['data'].get('_id')
|
||||
retorno['data']['nome_fantasia'] = empresa_nova.nome_fantasia
|
||||
retorno['data']['razao_social'] = empresa_nova.razao_social
|
||||
retorno['data']['status'] = empresa_nova.status
|
||||
retorno['data'].pop('razaoSocial', None)
|
||||
retorno['data'].pop('nomeFantasia', None)
|
||||
retorno['data'].pop('id_empresa', None)
|
||||
|
||||
return retorno
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro no banco ao cadastrar empresa: {e}")
|
||||
return {"error": f"Erro ao cadastrar empresa: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def get(self, cnpj: str = None, data: dict = None):
|
||||
try:
|
||||
if cnpj:
|
||||
resposta = self.api.get(f"{self.url}/{cnpj}")
|
||||
|
||||
if isinstance(resposta, dict) and "error" not in resposta:
|
||||
empresa_local = self.banco.session.query(EnterpriseTable).filter_by(cnpj=cnpj).first()
|
||||
if empresa_local:
|
||||
resposta["id"] = empresa_local.id
|
||||
resposta["nome_fantasia"] = empresa_local.nome_fantasia
|
||||
resposta["razao_social"] = empresa_local.razao_social
|
||||
resposta["status"] = empresa_local.status
|
||||
resposta.pop("razaoSocial", None)
|
||||
resposta.pop("nomeFantasia", None)
|
||||
|
||||
if "_id" in resposta:
|
||||
resposta["id_plugnotas"] = resposta["_id"]
|
||||
|
||||
else:
|
||||
resposta = self.api.get_paginated(self.url)
|
||||
# Injetar o ID do banco local em cada empresa listada
|
||||
if isinstance(resposta, dict) and "data" in resposta:
|
||||
for emp in resposta["data"]:
|
||||
empresa_local = self.banco.session.query(EnterpriseTable).filter_by(cnpj=emp.get("cpfCnpj")).first()
|
||||
if empresa_local:
|
||||
emp["id"] = empresa_local.id
|
||||
emp["nome_fantasia"] = empresa_local.nome_fantasia
|
||||
emp["razao_social"] = empresa_local.razao_social
|
||||
emp["status"] = empresa_local.status
|
||||
emp.pop("razaoSocial", None)
|
||||
emp.pop("nomeFantasia", None)
|
||||
if "_id" in emp:
|
||||
emp["id_plugnotas"] = emp["_id"]
|
||||
|
||||
self._log(RealmType.OPERATIONAL, f"Empresa consultada - CNPJ: {cnpj or 'TODAS'}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao buscar empresa: {e}")
|
||||
return {"error": f"Erro ao buscar empresa: {str(e)}"}
|
||||
|
||||
def post_file(self, file, cnpj, data=None):
|
||||
try:
|
||||
empresa = self.banco.session.query(EnterpriseTable).filter_by(cnpj=cnpj).first()
|
||||
if not empresa:
|
||||
self._log(RealmType.ERRORS, f"Empresa não encontrada para upload de logo - CNPJ: {cnpj}")
|
||||
return {"error": "Empresa não encontrada"}
|
||||
|
||||
logo = LogoTable(
|
||||
id_empresa=empresa.id,
|
||||
logo=file,
|
||||
status=True
|
||||
)
|
||||
self.banco.add(logo)
|
||||
|
||||
files = {'arquivo': ('logo.png', file, 'image/png')}
|
||||
resposta = self.api.post_file(file=files, url=f"{self.url}/{cnpj}/logotipo")
|
||||
self._log(RealmType.OPERATIONAL, f"Logo enviada com sucesso - CNPJ: {cnpj}")
|
||||
return resposta
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
self.banco.rollback()
|
||||
self._log(RealmType.ERRORS, f"Erro no banco ao salvar logo: {e}")
|
||||
return {"error": f"Erro ao salvar logo: {str(e)}"}
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro inesperado ao enviar logo: {e}")
|
||||
return {"error": f"Erro inesperado: {str(e)}"}
|
||||
|
||||
def patch_certificado(self, cnpj: str, certificado: str, data: dict):
|
||||
try:
|
||||
empresa = self.banco.session.query(EnterpriseTable).filter_by(cnpj=cnpj).first()
|
||||
if not empresa:
|
||||
self._log(RealmType.ERRORS, f"Empresa não encontrada para atualizar certificado - CNPJ: {cnpj}")
|
||||
return {"error": "Empresa não encontrada"}
|
||||
resposta = self.api.patch(url=f"{self.url}/{cnpj}/certificado", data={"certificado": certificado})
|
||||
self._log(RealmType.OPERATIONAL, f"Certificado da empresa atualizado - CNPJ: {cnpj}")
|
||||
return resposta
|
||||
except Exception as e:
|
||||
self._log(RealmType.ERRORS, f"Erro ao atualizar certificado: {e}")
|
||||
return {"error": f"Erro ao atualizar certificado: {str(e)}"}
|
||||
|
||||
21
endpoint_listener/src/endpoint_listener.py
Normal file
21
endpoint_listener/src/endpoint_listener.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import sys
|
||||
import os
|
||||
import uvicorn
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from listenerEndpoint import app
|
||||
|
||||
# O EndpointListener tem a função de manter a aplicação servindo de webhooks ativa recebendo HTTP e convertendo para RabbitMQ
|
||||
class EndpointListener:
|
||||
def __init__(self):
|
||||
self.host = "0.0.0.0"
|
||||
self.port = int(os.getenv("WEBHOOK_PORT", 8000))
|
||||
|
||||
def run(self):
|
||||
print(f"[STATUS] Endpoint Listener rodando na porta {self.port}...")
|
||||
uvicorn.run(app, host=self.host, port=self.port)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main = EndpointListener()
|
||||
main.run()
|
||||
170
endpoint_listener/src/listenerEndpoint.py
Normal file
170
endpoint_listener/src/listenerEndpoint.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
import uvicorn
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import RPCExchange2, QueueExchange2, LoggerExchange2, AppIC2Builder
|
||||
from utils.exchangeModels import RealmType
|
||||
|
||||
|
||||
# Campos obrigatórios que o PlugNotas envia no Endpoint
|
||||
CAMPOS_OBRIGATORIOS = ["id", "status", "documento"]
|
||||
|
||||
# Status válidos que o PlugNotas envia
|
||||
STATUS_VALIDOS = ["CONCLUIDO", "REJEITADO", "CANCELADO", "PROCESSANDO"]
|
||||
|
||||
# Tipos de documento/entidades aceitos
|
||||
DOCUMENTOS_VALIDOS = ["nfe", "nfse", "certificado_digital"]
|
||||
|
||||
# Variáveis globais
|
||||
redis_client = None
|
||||
system_config = None
|
||||
exchange = None
|
||||
logger = None
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def ciclo_app(app: FastAPI):
|
||||
# Gerencia o ciclo de vida da aplicação, inicializando conexões com Redis e RabbitMQ (Exchange) na inicialização
|
||||
global redis_client, system_config, exchange
|
||||
|
||||
try:
|
||||
redis_client = RedisClient(host=os.getenv('REDIS_HOST', 'localhost'), port=6379)
|
||||
system_config_data = redis_client.get("system_config")
|
||||
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis.")
|
||||
else:
|
||||
system_config = SystemConfig(**json.loads(system_config_data))
|
||||
|
||||
builder = AppIC2Builder(service_name="tecnospeed", versionstr="2.0.1", origin=system_config.origin)
|
||||
global logger
|
||||
logger = LoggerExchange2(ic2_builder=builder, amqps=system_config.amqps, system="integracao", service="tecnospeed", version="2.0.1", origin=system_config.origin)
|
||||
logger.logIC2(RealmType.LOGS, (f"[{system_config.origin}] Configuração lida com sucesso pelo Listener Endpoint.",))
|
||||
|
||||
exchange = QueueExchange2(
|
||||
amqps=system_config.amqps,
|
||||
origin=system_config.origin,
|
||||
system="integracao",
|
||||
service="endpoint_listener",
|
||||
version="2.0.1",
|
||||
client="#"
|
||||
)
|
||||
servicekey = system_config.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(serviceid=system_config.serviceid, servicekey=servicekey)
|
||||
logger.logIC2(RealmType.LOGS, ("Exchange inicializado com sucesso para o Listener Endpoint.",))
|
||||
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.logIC2(RealmType.ERRORS, (f"Erro ao inicializar o Listener Endpoint: {e}",))
|
||||
else:
|
||||
print(f"Erro ao inicializar o Listener Endpoint: {e}")
|
||||
|
||||
yield # Aplicação rodando
|
||||
|
||||
if logger:
|
||||
logger.logIC2(RealmType.LOGS, ("Listener Endpoint encerrando...",))
|
||||
logger.close()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Listener Endpoint PlugNotas",
|
||||
description="Endpoint para receber notificações Endpoint do PlugNotas",
|
||||
version="2.0.1",
|
||||
lifespan=ciclo_app
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def healthy():
|
||||
# Verifica a saúde da aplicação, validando se as dependências (Redis e Exchange) estão conectadas
|
||||
status = {
|
||||
"status": "ok",
|
||||
"redis": redis_client is not None,
|
||||
"exchange": exchange is not None,
|
||||
"system_config": system_config is not None
|
||||
}
|
||||
if not exchange:
|
||||
return JSONResponse(status_code=503, content={**status, "status": "degraded", "message": "Exchange não inicializado"})
|
||||
return status
|
||||
|
||||
|
||||
@app.post("/listenerPlugnotas")
|
||||
@app.post("/listenerPlugnotas/{cnpj}")
|
||||
async def receber_Endpoint_plugnotas(request: Request, cnpj: str = None):
|
||||
# Rota principal que recebe a notificação da PlugNotas e repassa para processamento no RabbitMQ
|
||||
try:
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception as e:
|
||||
logger.logIC2(RealmType.ERRORS, (f"Payload inválido recebido (não é JSON válido): {e}",))
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"status": "error", "message": "Payload não é um JSON válido"}
|
||||
)
|
||||
|
||||
documento = payload.get("documento", "desconhecido")
|
||||
status_nota = payload.get("status", "desconhecido")
|
||||
id_nota = payload.get("id", "")
|
||||
id_integracao = payload.get("idIntegracao", "")
|
||||
emitente = payload.get("emitente", cnpj or "")
|
||||
|
||||
logger.logIC2(RealmType.LOGS, (
|
||||
f"Endpoint recebido - Documento: {documento} | Status: {status_nota} | "
|
||||
f"ID: {id_nota} | IDIntegracao: {id_integracao} | Emitente: {emitente}",
|
||||
))
|
||||
|
||||
campos_faltantes = [campo for campo in CAMPOS_OBRIGATORIOS if campo not in payload]
|
||||
if campos_faltantes:
|
||||
logger.logIC2(RealmType.ERRORS, (
|
||||
f"Endpoint com campos obrigatórios faltantes: {campos_faltantes} | "
|
||||
f"Payload recebido: {json.dumps(payload)}",
|
||||
))
|
||||
return {"status": "warning", "message": f"Campos faltantes: {campos_faltantes}"}
|
||||
|
||||
if documento not in DOCUMENTOS_VALIDOS:
|
||||
logger.logIC2(RealmType.ERRORS, (
|
||||
f"Tipo de documento desconhecido: {documento} | Payload: {json.dumps(payload)}",
|
||||
))
|
||||
if status_nota not in STATUS_VALIDOS:
|
||||
logger.logIC2(RealmType.ERRORS, (
|
||||
f"Status desconhecido: {status_nota} | Documento: {documento} | ID: {id_nota}",
|
||||
))
|
||||
|
||||
if not exchange:
|
||||
logger.logIC2(RealmType.ERRORS, ("Exchange não inicializado no momento da chegada do Endpoint.",))
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": "Serviço temporariamente indisponível"}
|
||||
)
|
||||
|
||||
# Envia o EXATO objeto dict recebido do PlugNotas via Queue (assíncrono)
|
||||
try:
|
||||
exchange.sendMsg(json.dumps(payload), service="controller")
|
||||
|
||||
logger.logIC2(RealmType.LOGS, (
|
||||
f"Endpoint enviado à Fila (Queue) Controller. "
|
||||
f"Documento: {documento} | Status: {status_nota} | ID: {id_nota}",
|
||||
))
|
||||
|
||||
return {"status": "ok", "message": "Enviado à Fila com sucesso"}
|
||||
|
||||
except Exception as e:
|
||||
logger.logIC2(RealmType.ERRORS, (f"Erro ao postar mensagem na Fila (Controller): {e}",))
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": "Falha ao enviar mensagem para processamento"}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.logIC2(RealmType.ERRORS, (f"Erro inesperado no Listener Endpoint: {e}",))
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": "Erro interno no processamento do Endpoint"}
|
||||
)
|
||||
24
endpoint_listener/src/logs/integracao_01
Normal file
24
endpoint_listener/src/logs/integracao_01
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
2026-04-01 09:09:33,082 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-01 09:09:33.082579-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-01 09:09:33,700 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-01 09:09:33.700242-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-01 09:14:24,402 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-01 09:14:24.402193-03:00',
|
||||
'msg': 'Listener Webhook encerrando...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
194
endpoint_listener/src/logs/integracao_02
Normal file
194
endpoint_listener/src/logs/integracao_02
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
2026-04-02 09:14:16,362 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 09:14:16.362304-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 09:14:16,932 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 09:14:16.932459-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:14:30,014 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:14:30.013914-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:14:31,040 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:14:31.039753-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:22:20,024 - integracao - INFO - { 'caller': 'receber_webhook_plugnotas',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:22:20.024167-03:00',
|
||||
'msg': 'Webhook recebido - Documento: nfe | Status: CONCLUIDO | ID: '
|
||||
'69cab453... | IDIntegracao: | Emitente:',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:22:20,263 - integracao - INFO - { 'caller': 'receber_webhook_plugnotas',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:22:20.263204-03:00',
|
||||
'msg': 'Webhook repassado ao worker com sucesso - Documento: nfe | Status: '
|
||||
'CONCLUIDO | ID: 69cab453...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:23:45,608 - integracao - INFO - { 'caller': 'receber_webhook_plugnotas',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:23:45.608300-03:00',
|
||||
'msg': 'Webhook recebido - Documento: nfe | Status: CONCLUIDO | ID: '
|
||||
'69cab45329f21a7de7691961 | IDIntegracao: XXXYY99999_TESTE445 | '
|
||||
'Emitente: 08187168000160',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:23:46,107 - integracao - INFO - { 'caller': 'receber_webhook_plugnotas',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:23:46.107170-03:00',
|
||||
'msg': 'Webhook repassado ao worker com sucesso - Documento: nfe | Status: '
|
||||
'CONCLUIDO | ID: 69cab45329f21a7de7691961',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:26:07,402 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:26:07.401683-03:00',
|
||||
'msg': 'Listener Webhook encerrando...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:36,191 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:36.190896-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:36,969 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:36.969622-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:37,459 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:37.459047-03:00',
|
||||
'msg': 'Listener Webhook encerrando...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:42,582 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:42.582792-03:00',
|
||||
'msg': '[local] Configuração lida com sucesso pelo Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:43,391 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:43.391071-03:00',
|
||||
'msg': 'Exchange inicializado com sucesso para o Listener Webhook.',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 14:37:43,782 - integracao - INFO - { 'caller': 'ciclo_app',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 14:37:43.781995-03:00',
|
||||
'msg': 'Listener Webhook encerrando...',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:03:48,010 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:03:48.009993-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:03:48,217 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:03:48.216535-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:17,812 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:17.812369-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:18,488 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:18.487913-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:18,864 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:18.863889-03:00',
|
||||
'msg': 'Erro fatal ao conectar ao RabbitMQ - VERIFY PARAMETERS: '
|
||||
'is_amqp_url: True\n'
|
||||
' amqps[amqp://guest:guest@localhost:5672/]\n'
|
||||
' origin[local]\n'
|
||||
' system[integracao]\n'
|
||||
' service[tecnospeed]\n'
|
||||
' version[2.0.1\n'
|
||||
' client[]',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:30,426 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:30.426681-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-04-02 16:12:30,947 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-04-02 16:12:30.947199-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
56
endpoint_listener/src/logs/integracao_06
Normal file
56
endpoint_listener/src/logs/integracao_06
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
2026-04-06 08:25:17,847 - teste_wrapper.integracao - INFO - params: <URLParameters host=jackal.rmq.cloudamqp.com port=5671 virtual_host=iawkzmhh ssl=True> amqps: amqps://iawkzmhh:4iIVXCgLDsLy2oVyVWeV-o6shg-BZ89z@jackal.rmq.cloudamqp.com/iawkzmhh
|
||||
2026-04-06 08:25:18,327 - teste_wrapper.integracao - INFO - Logger Messages topics: logs.teste_wrapper.integracao.
|
||||
2026-04-06 08:25:18,361 - teste_wrapper.integracao - INFO - {'caller': 'IC2',
|
||||
'client': '',
|
||||
'hmac': '862c786853960277c4d518d1eb01143b2fe7782092dbf5403fb61b52127c7829',
|
||||
'logDate': '2026-04-06 08:25:18.361486-03:00',
|
||||
'msg': "LOGIC2 LOGS [('Testando o novo fo ...]",
|
||||
'origin': '',
|
||||
'payload': '44102feb05c76aac7b2b',
|
||||
'service': 'teste_wrapper',
|
||||
'serviceid': 'AF1699F719F1B6C81DE2BD38AB811994E3089B35C8E9314813A770AC7AE25D8D',
|
||||
'system': 'integracao',
|
||||
'target': '',
|
||||
'type': 'logger',
|
||||
'version': ''}
|
||||
2026-04-06 08:25:18,368 - teste_wrapper.integracao - INFO - {'caller': 'logMsg',
|
||||
'client': '',
|
||||
'hmac': '',
|
||||
'logDate': '2026-04-06 08:25:18.367868-03:00',
|
||||
'msg': 'Testando o novo formato IC2',
|
||||
'origin': '',
|
||||
'payload': '',
|
||||
'service': 'teste_wrapper',
|
||||
'serviceid': 'AF1699F719F1B6C81DE2BD38AB811994E3089B35C8E9314813A770AC7AE25D8D',
|
||||
'system': 'integracao',
|
||||
'target': '',
|
||||
'type': 'logger',
|
||||
'version': ''}
|
||||
2026-04-06 08:28:37,324 - teste_wrapper.integracao - INFO - params: <URLParameters host=jackal.rmq.cloudamqp.com port=5671 virtual_host=iawkzmhh ssl=True> amqps: amqps://iawkzmhh:4iIVXCgLDsLy2oVyVWeV-o6shg-BZ89z@jackal.rmq.cloudamqp.com/iawkzmhh
|
||||
2026-04-06 08:28:37,834 - teste_wrapper.integracao - INFO - Logger Messages topics: logs.teste_wrapper.integracao.
|
||||
2026-04-06 08:28:37,855 - teste_wrapper.integracao - INFO - {'caller': 'IC2',
|
||||
'client': '',
|
||||
'hmac': '841681411efce4ae99ff2602ae79da6284e6c3ca247a27da3a87dedf3771244e',
|
||||
'logDate': '2026-04-06 08:28:37.855637-03:00',
|
||||
'msg': "LOGIC2 LOGS [('Testando o formato ...]",
|
||||
'origin': '',
|
||||
'payload': '353fd214129406d78301',
|
||||
'service': 'teste_wrapper',
|
||||
'serviceid': 'AF1699F719F1B6C81DE2BD38AB811994E3089B35C8E9314813A770AC7AE25D8D',
|
||||
'system': 'integracao',
|
||||
'target': '',
|
||||
'type': 'logger',
|
||||
'version': ''}
|
||||
2026-04-06 08:28:37,865 - teste_wrapper.integracao - INFO - {'caller': 'logMsg',
|
||||
'client': '',
|
||||
'hmac': '',
|
||||
'logDate': '2026-04-06 08:28:37.865799-03:00',
|
||||
'msg': 'Testando o formato IC2',
|
||||
'origin': '',
|
||||
'payload': '',
|
||||
'service': 'teste_wrapper',
|
||||
'serviceid': 'AF1699F719F1B6C81DE2BD38AB811994E3089B35C8E9314813A770AC7AE25D8D',
|
||||
'system': 'integracao',
|
||||
'target': '',
|
||||
'type': 'logger',
|
||||
'version': ''}
|
||||
0
endpoint_listener/src/logs/integracao_25
Normal file
0
endpoint_listener/src/logs/integracao_25
Normal file
0
endpoint_listener/src/logs/integracao_26
Normal file
0
endpoint_listener/src/logs/integracao_26
Normal file
140
endpoint_listener/src/logs/integracao_27
Normal file
140
endpoint_listener/src/logs/integracao_27
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
2026-03-27 14:57:57,807 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-03-27 14:57:57.807604-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 14:57:58,282 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-03-27 14:57:58.282491-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 14:59:07,273 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 14:59:07.273291-03:00',
|
||||
'msg': "Configuração recebida - {'api_key': None, 'x_api_key': "
|
||||
"'2da392a6-79d2-4304-a8b7-959572c7e44d', 'user_postgres': "
|
||||
"'postgres', 'pass_postgres': 'postgres', 'host_postgres': "
|
||||
"'localhost', 'port_postgres': '5432', 'database_postgres': "
|
||||
"'tecnospeed'}",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 14:59:08,015 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 14:59:08.014993-03:00',
|
||||
'msg': 'Configuração incompleta',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:00:56,891 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:00:56.890566-03:00',
|
||||
'msg': "Configuração recebida - {'api_key': None, 'x_api_key': "
|
||||
"'2da392a6-79d2-4304-a8b7-959572c7e44d', 'user_postgres': "
|
||||
"'postgres', 'pass_postgres': 'postgres', 'host_postgres': "
|
||||
"'localhost', 'port_postgres': '5432', 'database_postgres': "
|
||||
"'sg_servico_tecnospeed'}",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:00:57,787 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:00:57.787472-03:00',
|
||||
'msg': 'Configuração incompleta',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:02:14,102 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:02:14.101859-03:00',
|
||||
'msg': "Configuração recebida - {'api_key': None, 'x_api_key': "
|
||||
"'2da392a6-79d2-4304-a8b7-959572c7e44d', 'user_postgres': "
|
||||
"'postgres', 'pass_postgres': 'postgres', 'host_postgres': "
|
||||
"'localhost', 'port_postgres': '5432', 'database_postgres': "
|
||||
"'tecnospeed'}",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:02:14,792 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:02:14.791942-03:00',
|
||||
'msg': 'Configuração incompleta',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:33,329 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-03-27 15:09:33.329870-03:00',
|
||||
'msg': 'Conectando ao RabbitMQ',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:33,698 - integracao - INFO - { 'caller': 'run',
|
||||
'client': '',
|
||||
'logDate': '2026-03-27 15:09:33.698183-03:00',
|
||||
'msg': "Configuração do sistema carregada - serviceid='local' "
|
||||
"servicekey='local' origin='local' "
|
||||
"amqps='amqp://guest:guest@localhost:5672/'",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:39,159 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:39.158573-03:00',
|
||||
'msg': "Configuração recebida - {'api_key': None, 'x_api_key': "
|
||||
"'2da392a6-79d2-4304-a8b7-959572c7e44d', 'user_postgres': "
|
||||
"'postgres', 'pass_postgres': 'postgres', 'host_postgres': "
|
||||
"'localhost', 'port_postgres': '5432', 'database_postgres': "
|
||||
"'tecnospeed'}",
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:44,975 - integracao - INFO - { 'caller': 'callback',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:44.975168-03:00',
|
||||
'msg': 'Configuração completa para o cliente Test',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:45,763 - integracao - INFO - { 'caller': 'nfe',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:45.763614-03:00',
|
||||
'msg': 'Iniciando serviço de NFe - Método: GET',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:46,610 - integracao - INFO - { 'caller': 'nfe',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:46.610671-03:00',
|
||||
'msg': 'Consultando resumo de NFe',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
2026-03-27 15:09:47,761 - integracao - INFO - { 'caller': 'nfe',
|
||||
'client': 'Test',
|
||||
'logDate': '2026-03-27 15:09:47.761417-03:00',
|
||||
'msg': 'Operação realizada com sucesso',
|
||||
'origin': '',
|
||||
'service': 'tecnospeed',
|
||||
'system': 'integracao',
|
||||
'version': '2.0.1'}
|
||||
1
endpoint_listener/src/tests/__init__.py
Normal file
1
endpoint_listener/src/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# tests package
|
||||
119
endpoint_listener/src/tests/conftest.py
Normal file
119
endpoint_listener/src/tests/conftest.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
"""
|
||||
Fixtures compartilhados para os testes E2E do servico_plugnotas.
|
||||
|
||||
Cria um banco SQLite in-memory, mocks do Logger e da Api,
|
||||
e uma instância pré-configurada da classe Main para cada teste.
|
||||
"""
|
||||
import pytest
|
||||
import json
|
||||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
mock_pika = MagicMock()
|
||||
sys.modules["pika"] = mock_pika
|
||||
|
||||
mock_redis_module = MagicMock()
|
||||
sys.modules["redis"] = mock_redis_module
|
||||
|
||||
from utils.banco import Base, Banco, LogTable
|
||||
from utils.api import Api
|
||||
|
||||
@pytest.fixture
|
||||
def banco_teste():
|
||||
"""Cria um Banco real com SQLite in-memory para cada teste (isolado)."""
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
Base.metadata.create_all(engine)
|
||||
Session = sessionmaker(bind=engine)
|
||||
|
||||
banco = Banco.__new__(Banco)
|
||||
banco.engine = engine
|
||||
banco.session = Session()
|
||||
|
||||
yield banco
|
||||
|
||||
banco.session.close()
|
||||
engine.dispose()
|
||||
|
||||
@pytest.fixture
|
||||
def mock_logger():
|
||||
"""Logger que grava todas as chamadas de log em uma lista para inspeção."""
|
||||
logger = MagicMock()
|
||||
logger.logs = []
|
||||
logger.client = "TestClient"
|
||||
|
||||
def capture_log(realm, msg_tuple):
|
||||
logger.logs.append({"realm": str(realm), "message": msg_tuple[0] if msg_tuple else ""})
|
||||
|
||||
logger.logIC2.side_effect = capture_log
|
||||
return logger
|
||||
|
||||
@pytest.fixture
|
||||
def mock_api():
|
||||
"""Api que retorna respostas configuráveis sem fazer chamadas HTTP reais."""
|
||||
api = MagicMock(spec=Api)
|
||||
api.get.return_value = {"status": "ok"}
|
||||
api.post.return_value = {"documents": [{"id": "PLUG-001"}]}
|
||||
api.put.return_value = {"status": "ok"}
|
||||
api.patch.return_value = {"status": "ok"}
|
||||
api.delete.return_value = {"status": "ok"}
|
||||
api.post_file.return_value = {"status": "ok"}
|
||||
api.post_form_data.return_value = {"data": {"id": "CERT-001"}}
|
||||
api.get_paginated.return_value = {"data": [], "total": 0}
|
||||
return api
|
||||
|
||||
@pytest.fixture
|
||||
def mock_exchange():
|
||||
"""Exchange que simula setPayload sem enviar nada."""
|
||||
exchange = MagicMock()
|
||||
exchange.payload = ""
|
||||
exchange.payloads_set = []
|
||||
|
||||
def capture_payload(p):
|
||||
exchange.payloads_set.append(p)
|
||||
exchange.payload = p
|
||||
|
||||
exchange.setPayload.side_effect = capture_payload
|
||||
return exchange
|
||||
|
||||
@pytest.fixture
|
||||
def main_instance(banco_teste, mock_logger, mock_exchange):
|
||||
with patch("utils.Exchange2.RPCExchange2"), \
|
||||
patch("utils.Exchange2.QueueExchange2"), \
|
||||
patch("utils.Exchange2.LoggerExchange2"), \
|
||||
patch("utils.redis.RedisClient"):
|
||||
|
||||
from main import Main
|
||||
|
||||
with patch.object(Main, '__init__', lambda self: None):
|
||||
main = Main()
|
||||
|
||||
main.client = "TestClient"
|
||||
main.api_key = "test-api-key-fake"
|
||||
main.exchange = mock_exchange
|
||||
main.banco = banco_teste
|
||||
main.system_config = MagicMock()
|
||||
main.system_config.serviceid = "test-id"
|
||||
main.system_config.servicekey = "test-key-32-chars-long-12345678"
|
||||
main.redis = MagicMock()
|
||||
main.service = "tecnospeed"
|
||||
main.system = "integracao"
|
||||
main.version = "2.0.1-test"
|
||||
main.logger = mock_logger
|
||||
main.url_base = "https://api.sandbox.plugnotas.com.br"
|
||||
|
||||
main.funcoes = {
|
||||
"nfe": main.nfe,
|
||||
"nfse": main.nfse,
|
||||
"certificado_digital": main.certificado_digital,
|
||||
"empresa": main.empresa,
|
||||
"servico": main.servico,
|
||||
"tomador": main.tomador,
|
||||
"relatorios": main.relatorios,
|
||||
"webhooks": main.webhooks,
|
||||
}
|
||||
|
||||
return main
|
||||
|
||||
67
endpoint_listener/src/tests/debug_payload.py
Normal file
67
endpoint_listener/src/tests/debug_payload.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Garante que o Python encontre os módulos do projeto
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from Models.payload import validate_payload
|
||||
|
||||
sample_payload = {
|
||||
"fila": "nfse",
|
||||
"cliente": "MassiveNFSeTest",
|
||||
"metodo": "POST",
|
||||
"dados": {
|
||||
"id_empresa": "some-uuid",
|
||||
"id_usuario": 1,
|
||||
"idIntegracao": "TEST_123",
|
||||
"prestador": {
|
||||
"cpfCnpj": "00000000000000"
|
||||
},
|
||||
"tomador": {
|
||||
"cpfCnpj": "08114280956",
|
||||
"razaoSocial": "NFSE 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",
|
||||
"tipoLogradouro": "Avenida"
|
||||
}
|
||||
},
|
||||
"servico": {
|
||||
"codigo": "1.01",
|
||||
"codigoTributacao": "1.01",
|
||||
"discriminacao": "SERVICO DE TESTE DE CARGA MASSIVA",
|
||||
"cnae": "6201501",
|
||||
"iss": {
|
||||
"tipoTributacao": 1,
|
||||
"exigibilidade": 1,
|
||||
"aliquota": 2.0
|
||||
},
|
||||
"valor": {
|
||||
"servico": 100.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"x_api_key": "2da392a6-79d2-4304-a8b7-959572c7e44d",
|
||||
"user_postgres": "tecnospeed",
|
||||
"pass_postgres": "u7QimG7E!SF//JC2c8",
|
||||
"host_postgres": "banco",
|
||||
"port_postgres": "5432",
|
||||
"database_postgres": "tecnospeed"
|
||||
},
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br"
|
||||
}
|
||||
|
||||
try:
|
||||
print("Tentando validar payload...")
|
||||
validated = validate_payload(sample_payload)
|
||||
print("Sucesso! Payload válido.")
|
||||
except Exception as e:
|
||||
print(f"Erro de validação: {e}")
|
||||
33
endpoint_listener/src/tests/debug_report_api.py
Normal file
33
endpoint_listener/src/tests/debug_report_api.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import requests
|
||||
import os
|
||||
import json
|
||||
|
||||
def debug_report():
|
||||
api_key = "2da392a6-79d2-4304-a8b7-959572c7e44d" # From src/utils/api.py comment
|
||||
url = "https://api.sandbox.plugnotas.com.br/nfse/relatorio?from=2026-04-01&to=2026-04-30"
|
||||
|
||||
headers = {
|
||||
"x-api-key": api_key
|
||||
}
|
||||
|
||||
print(f"Testing URL: {url}")
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
print(f"Status Code: {response.status_code}")
|
||||
try:
|
||||
print(f"Response Body: {json.dumps(response.json(), indent=2)}")
|
||||
except:
|
||||
print(f"Response Body: {response.text}")
|
||||
|
||||
# Test with ISO format
|
||||
url_iso = "https://api.sandbox.plugnotas.com.br/nfse/relatorio?from=2026-04-01T00:00:00&to=2026-04-30T23:59:59"
|
||||
print(f"\nTesting URL (ISO): {url_iso}")
|
||||
response = requests.get(url_iso, headers=headers)
|
||||
print(f"Status Code: {response.status_code}")
|
||||
try:
|
||||
print(f"Response Body: {json.dumps(response.json(), indent=2)}")
|
||||
except:
|
||||
print(f"Response Body: {response.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_report()
|
||||
46
endpoint_listener/src/tests/purge_queue.py
Normal file
46
endpoint_listener/src/tests/purge_queue.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import pika
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
||||
# Garante que o Python encontre os módulos do projeto
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from utils.redis import RedisClient
|
||||
|
||||
def purge_queue():
|
||||
# 1. Obter URL do RabbitMQ do Redis
|
||||
redis_host = os.getenv('REDIS_HOST', 'localhost')
|
||||
redis_client = RedisClient(host=redis_host, port=6379)
|
||||
try:
|
||||
system_config_data = redis_client.get("system_config")
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis.")
|
||||
return
|
||||
system_config = json.loads(system_config_data)
|
||||
amqps_url = system_config.get("amqps")
|
||||
except Exception as e:
|
||||
print(f"Erro ao ler configs do Redis: {e}")
|
||||
return
|
||||
|
||||
if not amqps_url:
|
||||
print("Erro: AMQPS URL não encontrada na configuração.")
|
||||
return
|
||||
|
||||
# 2. Conectar e limpar a fila
|
||||
queue_name = "integracao_tecnospeed"
|
||||
try:
|
||||
params = pika.URLParameters(amqps_url)
|
||||
connection = pika.BlockingConnection(params)
|
||||
channel = connection.channel()
|
||||
|
||||
# Tenta realizar o purge
|
||||
result = channel.queue_purge(queue=queue_name)
|
||||
print(f"Sucesso! Fila [{queue_name}] limpa. Mensagens removidas: {result.method.message_count}")
|
||||
|
||||
connection.close()
|
||||
except Exception as e:
|
||||
print(f"Erro ao limpar a fila: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
purge_queue()
|
||||
134
endpoint_listener/src/tests/test.py
Normal file
134
endpoint_listener/src/tests/test.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from Models.config import SystemConfig
|
||||
from utils.redis import RedisClient
|
||||
from utils.Exchange2 import RPCExchange2
|
||||
|
||||
def main():
|
||||
print("Iniciando script de testes da mensageria...")
|
||||
|
||||
# Carregando configuração do Redis
|
||||
redis_client = RedisClient(host=os.getenv('REDIS_HOST', 'localhost'), port=6379)
|
||||
try:
|
||||
system_config_data = redis_client.get("system_config")
|
||||
if not system_config_data:
|
||||
print("Erro: system_config não encontrado no Redis. Certifique-se de que a variável existe.")
|
||||
return
|
||||
|
||||
system_config = SystemConfig(**json.loads(system_config_data))
|
||||
print(f"[{system_config.origin}] Configuração do sistema lida com sucesso.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erro ao ler configs do Redis: {e}")
|
||||
return
|
||||
|
||||
# Inicializando mensageria RPC
|
||||
try:
|
||||
amqps_local = system_config.amqps.replace("host.docker.internal", "localhost")
|
||||
exchange = RPCExchange2(
|
||||
amqps=amqps_local,
|
||||
origin=system_config.origin,
|
||||
system="integracao",
|
||||
service="tecnospeed",
|
||||
version="2.0.1",
|
||||
client="#"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Falha ao inicializar mensageria: {e}")
|
||||
return
|
||||
|
||||
# Configurando criptografia e payload
|
||||
servicekey = system_config.servicekey.ljust(32, '0')[:32]
|
||||
exchange.setCrypto(serviceid=system_config.serviceid, servicekey=servicekey)
|
||||
|
||||
# Payload da mensageria
|
||||
|
||||
payload_interno = {
|
||||
"fila": "nfse",
|
||||
"cliente": "Test",
|
||||
"metodo": "POST",
|
||||
"dados": {
|
||||
"idIntegracao": "INTEGRACAO-NFSE-002",
|
||||
"prestador": {
|
||||
"cpfCnpj": "08187168000160"
|
||||
},
|
||||
"tomador": {
|
||||
"cpfCnpj": "08114280956",
|
||||
"razaoSocial": "João Tomador da Silva",
|
||||
"inscricaoMunicipal": "isento",
|
||||
"email": "tomador@email.com",
|
||||
"endereco": {
|
||||
"descricaoCidade": "São Paulo",
|
||||
"cep": "00000000",
|
||||
"tipoLogradouro": "Rua",
|
||||
"logradouro": "Dois de Ouro",
|
||||
"tipoBairro": "Bairro",
|
||||
"codigoCidade": "3550308",
|
||||
"complemento": "Apto 13",
|
||||
"estado": "SP",
|
||||
"numero": "44",
|
||||
"bairro": "Centro"
|
||||
}
|
||||
},
|
||||
"servico": {
|
||||
"codigo": "1.01",
|
||||
"codigoTributacao": "1.01",
|
||||
"discriminacao": "Serviços prestados de tecnologia",
|
||||
"cnae": "6204000",
|
||||
"iss": {
|
||||
"tipoTributacao": 6,
|
||||
"exigibilidade": 1,
|
||||
"aliquota": 2.5
|
||||
},
|
||||
"valor": {
|
||||
"servico": 1500.50,
|
||||
"descontoCondicionado": 0.0,
|
||||
"descontoIncondicionado": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"x_api_key": os.getenv('PLUGNOTAS_API_KEY', ''),
|
||||
"user_postgres": os.getenv('POSTGRES_USER', 'tecnospeed'),
|
||||
"pass_postgres": os.getenv('POSTGRES_PASSWORD', 'tecnospeed'),
|
||||
"host_postgres": os.getenv('POSTGRES_HOST', 'localhost'),
|
||||
"port_postgres": os.getenv('POSTGRES_PORT', '5432'),
|
||||
"database_postgres": os.getenv('POSTGRES_DB', 'tecnospeed')
|
||||
},
|
||||
"url_base": "https://api.sandbox.plugnotas.com.br"
|
||||
}
|
||||
|
||||
|
||||
# Criptografando e enviando tarefa
|
||||
exchange.setPayload(json.dumps(payload_interno))
|
||||
|
||||
print("Disparando chamada RPC para o RabbitMQ...")
|
||||
# Executando chamada RPC
|
||||
response = exchange.callRPC("Disparo de testes executado pelo test.py")
|
||||
print("\n[RAW RPC RESPONSE]:")
|
||||
print(response)
|
||||
|
||||
# Descriptografando a resposta
|
||||
try:
|
||||
resp_obj = json.loads(response)
|
||||
hmac_resp = resp_obj.get('hmac')
|
||||
payload_resp = resp_obj.get('payload')
|
||||
|
||||
if hmac_resp and payload_resp:
|
||||
if exchange.getPayload(hmac_resp, payload_resp):
|
||||
print("\n[DECRYPTED RPC DATA]:")
|
||||
print(json.dumps(json.loads(exchange.payload), indent=2))
|
||||
else:
|
||||
print("\n[ERROR]: Failed to decrypt response.")
|
||||
else:
|
||||
print("\n[INFO]: Response does not contain an encrypted payload.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n[ERROR]: Error processing response: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
58
endpoint_listener/src/tests/testArch.py
Normal file
58
endpoint_listener/src/tests/testArch.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from fastapi.testclient import TestClient
|
||||
from src.listenerEndpoint import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
PAYLOAD_VALIDO = {
|
||||
"id": "60a7c4f1c9d2a00015...",
|
||||
"status": "CONCLUIDO",
|
||||
"documento": "nfe"
|
||||
}
|
||||
HEADERS_VALIDOS = {"x-plugnotas-signature": "assinatura_hmac_valida"}
|
||||
|
||||
class TestPlugNotasWebhookEndpoint:
|
||||
|
||||
@patch("src.listenerEndpoint.exchange")
|
||||
def test_deve_receber_payload_e_enfileirar_com_sucesso(self, mock_exchange):
|
||||
"""Critério: Caminho feliz. Valida, enfileira e responde com sucesso (200)."""
|
||||
mock_exchange.sendTask.return_value = True
|
||||
|
||||
response = client.post("/listenerPlugnotas", json=PAYLOAD_VALIDO, headers=HEADERS_VALIDOS)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"status": "ok", "message": "Webhook recebido e encaminhado para processamento"}
|
||||
|
||||
mock_exchange.setPayload.assert_called_once()
|
||||
mock_exchange.sendTask.assert_called_once_with("Webhook PlugNotas Recebido")
|
||||
|
||||
@patch("src.listenerEndpoint.exchange")
|
||||
def test_deve_rejeitar_payload_invalido_antes_de_processar(self, mock_exchange):
|
||||
"""Critério: Integridade de Dados. Valida campos obrigatórios recebidos do Hook."""
|
||||
payload_invalido = {"status": "CONCLUIDO"} # Faltando ID e documento
|
||||
|
||||
response = client.post("/listenerPlugnotas", json=payload_invalido, headers=HEADERS_VALIDOS)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "warning"
|
||||
assert "Campos faltantes" in response.json()["message"]
|
||||
|
||||
@patch("src.listenerEndpoint.exchange")
|
||||
def test_deve_retornar_erro_se_internal_error_no_rabbitmq(self, mock_exchange):
|
||||
"""Critério: Resiliência. Retorna 500 em caso de erro ao enviar para fila."""
|
||||
# Simulando uma queda ao enviar task
|
||||
mock_exchange.sendTask.side_effect = Exception("RabbitMQ is down")
|
||||
|
||||
response = client.post("/listenerPlugnotas", json=PAYLOAD_VALIDO, headers=HEADERS_VALIDOS)
|
||||
|
||||
assert response.status_code == 500
|
||||
assert response.json()["message"] == "Erro interno no processamento do webhook"
|
||||
|
||||
def test_deve_retornar_erro_se_exchange_nao_inicializado(self):
|
||||
"""Critério: Resiliência. Retorna 500 se o exchange global for nulo."""
|
||||
# Sem patch de exchange, então pegará o exchange default (None) em client sem lifespan completo
|
||||
response = client.post("/listenerPlugnotas", json=PAYLOAD_VALIDO)
|
||||
|
||||
assert response.status_code == 500
|
||||
assert response.json()["message"] == "Serviço temporariamente indisponível"
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue