781 lines
30 KiB
Python
781 lines
30 KiB
Python
import requests
|
|
import pandas as pd
|
|
import json
|
|
import os
|
|
import time
|
|
import pyodbc
|
|
import base64
|
|
import xml.etree.ElementTree as ET
|
|
from datetime import datetime, timedelta, timezone
|
|
from requests.adapters import HTTPAdapter
|
|
from urllib3.util.retry import Retry
|
|
|
|
# =============================================================================
|
|
# CONFIGURAÇÕES
|
|
# =============================================================================
|
|
|
|
# Configurações da API
|
|
API_URL = "https://api.arquivei.com.br/v1/nfe/received"
|
|
API_HEADERS = {
|
|
"X-API-ID": "3e51eeaeb4c678bb648801cbc545da9cc75682cf",
|
|
"X-API-KEY": "73d6941b0c948ac010b35c4f57506072dac44a4f",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
# CNPJs permitidos para filtragem
|
|
CNPJS_PERMITIDOS = {'06147451000990', '06147451000809'}
|
|
|
|
# Quantidade de dias para buscar (hoje + X dias para trás)
|
|
DIAS_PARA_BUSCAR = 1 # Altere aqui para mudar o período
|
|
|
|
# =============================================================================
|
|
# FUNÇÕES AUXILIARES
|
|
# =============================================================================
|
|
|
|
def create_session_with_retry():
|
|
"""Cria uma sessão de requests com configuração de retry e timeout"""
|
|
session = requests.Session()
|
|
|
|
try:
|
|
retry_strategy = Retry(
|
|
total=3,
|
|
status_forcelist=[429, 500, 502, 503, 504],
|
|
allowed_methods=["HEAD", "GET", "OPTIONS"],
|
|
backoff_factor=1
|
|
)
|
|
except TypeError:
|
|
try:
|
|
retry_strategy = Retry(
|
|
total=3,
|
|
status_forcelist=[429, 500, 502, 503, 504],
|
|
method_whitelist=["HEAD", "GET", "OPTIONS"],
|
|
backoff_factor=1
|
|
)
|
|
except TypeError:
|
|
retry_strategy = Retry(
|
|
total=3,
|
|
status_forcelist=[429, 500, 502, 503, 504],
|
|
backoff_factor=1
|
|
)
|
|
|
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
session.mount("http://", adapter)
|
|
session.mount("https://", adapter)
|
|
|
|
return session
|
|
|
|
def get_db_connection():
|
|
"""Configuração da conexão SQL Server"""
|
|
return pyodbc.connect(
|
|
'DRIVER={ODBC Driver 18 for SQL Server};'
|
|
'SERVER=10.77.77.10;'
|
|
'DATABASE=GINSENG;'
|
|
'UID=supginseng;'
|
|
'PWD=Iphone2513@;'
|
|
'PORT=1433;'
|
|
'TrustServerCertificate=yes'
|
|
)
|
|
|
|
def fazer_requisicao_robusta(session, url, headers, params, max_tentativas=3):
|
|
"""Faz uma requisição HTTP com tratamento robusto de timeout e erros"""
|
|
for tentativa in range(max_tentativas):
|
|
try:
|
|
print(f" Tentativa {tentativa + 1}/{max_tentativas}...", end=" ", flush=True)
|
|
response = session.get(url, headers=headers, params=params, timeout=(30, 60))
|
|
|
|
if response.status_code == 200:
|
|
print("Sucesso!")
|
|
return response
|
|
else:
|
|
print(f"Erro {response.status_code}")
|
|
if tentativa < max_tentativas - 1:
|
|
wait_time = (tentativa + 1) * 2
|
|
print(f" Aguardando {wait_time} segundos...")
|
|
time.sleep(wait_time)
|
|
|
|
except requests.exceptions.ConnectTimeout:
|
|
print("Timeout de conexão!")
|
|
if tentativa < max_tentativas - 1:
|
|
time.sleep((tentativa + 1) * 5)
|
|
|
|
except requests.exceptions.ReadTimeout:
|
|
print("Timeout de leitura!")
|
|
if tentativa < max_tentativas - 1:
|
|
time.sleep((tentativa + 1) * 3)
|
|
|
|
except requests.exceptions.ConnectionError as e:
|
|
print(f"Erro de conexão: {str(e)[:100]}...")
|
|
if tentativa < max_tentativas - 1:
|
|
time.sleep((tentativa + 1) * 5)
|
|
|
|
except Exception as e:
|
|
print(f"Erro inesperado: {str(e)[:100]}...")
|
|
if tentativa < max_tentativas - 1:
|
|
time.sleep((tentativa + 1) * 2)
|
|
|
|
print(" Todas as tentativas falharam")
|
|
return None
|
|
|
|
# =============================================================================
|
|
# PARTE 1: BUSCAR NOTAS E IDENTIFICAR NOVAS CHAVES
|
|
# =============================================================================
|
|
|
|
def consultar_chaves_existentes(chaves_list):
|
|
"""Consulta quais chaves já existem no banco de dados"""
|
|
if not chaves_list:
|
|
return set()
|
|
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
chaves_str = "', '".join(chaves_list)
|
|
query = f"""
|
|
SELECT DISTINCT [chave]
|
|
FROM [GINSENG].[dbo].[fato_notas_entrada]
|
|
WHERE [chave] IN ('{chaves_str}')
|
|
"""
|
|
|
|
cursor.execute(query)
|
|
chaves_existentes = {row[0] for row in cursor.fetchall()}
|
|
|
|
cursor.close()
|
|
conn.close()
|
|
|
|
print(f" Consulta no banco: {len(chaves_existentes):,} chaves já existem de {len(chaves_list):,} consultadas")
|
|
return chaves_existentes
|
|
|
|
except Exception as e:
|
|
print(f" Erro ao consultar banco de dados: {e}")
|
|
return set()
|
|
|
|
def inserir_nfes_banco(registros_novos):
|
|
"""Insere os registros novos de NFe no banco de dados"""
|
|
if not registros_novos:
|
|
return 0
|
|
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
insert_query = """
|
|
INSERT INTO [GINSENG].[dbo].[fato_notas_entrada] (
|
|
[chave], [cnf], [serie], [data_emissao], [hora_emissao], [cnpj_emissor],
|
|
[nome_emissor], [cnpj_destinatario], [valor_total_produtos], [valor_icmsst],
|
|
[valor_fcpst], [valor_frete], [valor_seguro], [valor_outras_despesas],
|
|
[valor_ii], [valor_ipi], [valor_ipi_devol], [valor_servicos], [valor_desconto],
|
|
[valor_icms_desonerado], [valor_liquido], [tipo_pagamento_json], [numero_fatura],
|
|
[qtd_parcelas], [duplicatas_json], [valor_icms], [situacao], [TRIAL119]
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
"""
|
|
|
|
registros_inseridos = 0
|
|
for registro in registros_novos:
|
|
try:
|
|
cursor.execute(insert_query, (
|
|
registro.get('chave', ''),
|
|
registro.get('cnf', ''),
|
|
registro.get('serie', ''),
|
|
registro.get('data_emissao', None),
|
|
registro.get('hora_emissao', None),
|
|
registro.get('cnpj_emissor', ''),
|
|
registro.get('nome_emissor', ''),
|
|
registro.get('cnpj_destinatario', ''),
|
|
float(registro.get('valor_total_produtos', 0)) if registro.get('valor_total_produtos') else None,
|
|
float(registro.get('valor_icmsst', 0)) if registro.get('valor_icmsst') else None,
|
|
float(registro.get('valor_fcpst', 0)) if registro.get('valor_fcpst') else None,
|
|
float(registro.get('valor_frete', 0)) if registro.get('valor_frete') else None,
|
|
float(registro.get('valor_seguro', 0)) if registro.get('valor_seguro') else None,
|
|
float(registro.get('valor_outras_despesas', 0)) if registro.get('valor_outras_despesas') else None,
|
|
float(registro.get('valor_ii', 0)) if registro.get('valor_ii') else None,
|
|
float(registro.get('valor_ipi', 0)) if registro.get('valor_ipi') else None,
|
|
float(registro.get('valor_ipi_devol', 0)) if registro.get('valor_ipi_devol') else None,
|
|
float(registro.get('valor_servicos', 0)) if registro.get('valor_servicos') else None,
|
|
float(registro.get('valor_desconto', 0)) if registro.get('valor_desconto') else None,
|
|
float(registro.get('valor_icms_desonerado', 0)) if registro.get('valor_icms_desonerado') else None,
|
|
float(registro.get('valor_liquido', 0)) if registro.get('valor_liquido') else None,
|
|
registro.get('tipo_pagamento_json', ''),
|
|
registro.get('numero_fatura', ''),
|
|
int(registro.get('qtd_parcelas', 0)) if registro.get('qtd_parcelas') else None,
|
|
registro.get('duplicatas_json', ''),
|
|
float(registro.get('valor_icms', 0)) if registro.get('valor_icms') else None,
|
|
registro.get('situacao', ''),
|
|
registro.get('TRIAL119', '')
|
|
))
|
|
registros_inseridos += 1
|
|
except Exception as e:
|
|
print(f" Erro ao inserir registro {registro.get('chave', '')}: {e}")
|
|
|
|
conn.commit()
|
|
cursor.close()
|
|
conn.close()
|
|
|
|
return registros_inseridos
|
|
|
|
except Exception as e:
|
|
print(f" Erro ao conectar com banco de dados: {e}")
|
|
return 0
|
|
|
|
def obter_total_registros_dia(session, data_str):
|
|
"""Consulta quantos registros existem em um dia específico"""
|
|
params = {
|
|
"created_at[from]": data_str,
|
|
"created_at[to]": data_str,
|
|
"format_type": "JSON",
|
|
"limit": 1
|
|
}
|
|
|
|
response = fazer_requisicao_robusta(session, API_URL, API_HEADERS, params, max_tentativas=2)
|
|
|
|
if response and response.status_code == 200:
|
|
data = response.json()
|
|
return data.get('count', 0)
|
|
return 0
|
|
|
|
def processar_dia_chaves(session, data_str, all_extracted_data):
|
|
"""Processa todos os registros de um dia específico para extrair chaves"""
|
|
params = {
|
|
"created_at[from]": data_str,
|
|
"created_at[to]": data_str,
|
|
"format_type": "JSON",
|
|
"limit": 50
|
|
}
|
|
|
|
registros_dia = 0
|
|
registros_filtrados = 0
|
|
page_count = 0
|
|
|
|
while True:
|
|
page_count += 1
|
|
print(f" Página {page_count}...", end=" ")
|
|
|
|
response = fazer_requisicao_robusta(session, API_URL, API_HEADERS, params, max_tentativas=3)
|
|
|
|
if not response or response.status_code != 200:
|
|
print(f"Falha")
|
|
break
|
|
|
|
data = response.json()
|
|
nfe_list = data.get('data', [])
|
|
|
|
if not nfe_list:
|
|
print(f"Fim dos dados")
|
|
break
|
|
|
|
print(f"{len(nfe_list)} registros")
|
|
|
|
for nfe_data in nfe_list:
|
|
registros_dia += 1
|
|
|
|
nfe = nfe_data.get('xml', {}).get('NFe', {})
|
|
inf_nfe = nfe.get('infNFe', {})
|
|
ide = inf_nfe.get('ide', {})
|
|
emit = inf_nfe.get('emit', {})
|
|
dest = inf_nfe.get('dest', {})
|
|
total = inf_nfe.get('total', {}).get('ICMSTot', {})
|
|
cobr = inf_nfe.get('cobr', {})
|
|
|
|
cnpj_emissor = str(emit.get('CNPJ', '')).strip()
|
|
if cnpj_emissor not in CNPJS_PERMITIDOS:
|
|
continue
|
|
|
|
registros_filtrados += 1
|
|
|
|
registro = {
|
|
'chave': str(nfe_data.get('access_key', '')).strip(),
|
|
'cnf': str(ide.get('cNF', '')).strip(),
|
|
'serie': str(ide.get('serie', '')).strip(),
|
|
'data_emissao': ide.get('dhEmi', '').split('T')[0] if ide.get('dhEmi') else '',
|
|
'hora_emissao': ide.get('dhEmi', '').split('T')[1].split('-')[0] if ide.get('dhEmi') and 'T' in ide.get('dhEmi', '') else '',
|
|
'cnpj_emissor': cnpj_emissor,
|
|
'nome_emissor': str(emit.get('xNome', '')).strip(),
|
|
'cnpj_destinatario': str(dest.get('CNPJ', '')).strip(),
|
|
'valor_total_produtos': str(total.get('vProd', '')).strip(),
|
|
'valor_icmsst': str(total.get('vST', '')).strip(),
|
|
'valor_fcpst': str(total.get('vFCPST', '')).strip(),
|
|
'valor_frete': str(total.get('vFrete', '')).strip(),
|
|
'valor_seguro': str(total.get('vSeg', '')).strip(),
|
|
'valor_outras_despesas': str(total.get('vOutro', '')).strip(),
|
|
'valor_ii': str(total.get('vII', '')).strip(),
|
|
'valor_ipi': str(total.get('vIPI', '')).strip(),
|
|
'valor_ipi_devol': str(total.get('vIPIDevol', '')).strip(),
|
|
'valor_servicos': '',
|
|
'valor_desconto': str(total.get('vDesc', '')).strip(),
|
|
'valor_icms_desonerado': str(total.get('vICMSDeson', '')).strip(),
|
|
'valor_liquido': str(total.get('vNF', '')).strip(),
|
|
'tipo_pagamento_json': json.dumps(inf_nfe.get('pag', {}), ensure_ascii=False) if inf_nfe.get('pag') else '',
|
|
'numero_fatura': str(cobr.get('fat', {}).get('nFat', '')).strip() if cobr.get('fat') else '',
|
|
'qtd_parcelas': len(cobr.get('dup', [])) if cobr.get('dup') else 0,
|
|
'duplicatas_json': json.dumps(cobr.get('dup', []), ensure_ascii=False) if cobr.get('dup') else '',
|
|
'valor_icms': str(total.get('vICMS', '')).strip(),
|
|
'situacao': str(inf_nfe.get('protNFe', {}).get('infProt', {}).get('cStat', '')).strip() if inf_nfe.get('protNFe') else ''
|
|
}
|
|
|
|
all_extracted_data.append(registro)
|
|
|
|
page_info = data.get('page', {})
|
|
next_url = page_info.get('next')
|
|
|
|
if not next_url:
|
|
break
|
|
|
|
if 'cursor=' in next_url:
|
|
cursor_value = next_url.split('cursor=')[1].split('&')[0]
|
|
params['cursor'] = cursor_value
|
|
else:
|
|
break
|
|
|
|
time.sleep(0.3)
|
|
|
|
return registros_filtrados
|
|
|
|
def buscar_notas_e_identificar_novas():
|
|
"""
|
|
PARTE 1: Busca notas no período e retorna lista de chaves novas
|
|
Retorna: lista de chaves que não existem no banco
|
|
"""
|
|
print("\n" + "=" * 70)
|
|
print(" PARTE 1: BUSCANDO NOTAS E IDENTIFICANDO CHAVES NOVAS")
|
|
print("=" * 70)
|
|
|
|
data_atual = datetime.now()
|
|
data_inicio = data_atual - timedelta(days=DIAS_PARA_BUSCAR)
|
|
|
|
print(f" Período: {data_inicio.strftime('%Y-%m-%d')} até {data_atual.strftime('%Y-%m-%d')}")
|
|
print(f" CNPJs permitidos: {', '.join(CNPJS_PERMITIDOS)}")
|
|
|
|
session = create_session_with_retry()
|
|
|
|
# Analisar dias com dados
|
|
print(f"\n Analisando período por dia...")
|
|
detalhes_dias = []
|
|
current_date = data_inicio
|
|
|
|
while current_date <= data_atual:
|
|
data_str = current_date.strftime("%Y-%m-%d")
|
|
print(f" Consultando {data_str}...", end=" ")
|
|
registros = obter_total_registros_dia(session, data_str)
|
|
if registros > 0:
|
|
detalhes_dias.append((data_str, registros))
|
|
print(f"{registros:,} registros")
|
|
else:
|
|
print("0 registros")
|
|
current_date += timedelta(days=1)
|
|
time.sleep(0.3)
|
|
|
|
if not detalhes_dias:
|
|
print(" Nenhum registro encontrado no período.")
|
|
return []
|
|
|
|
# Extrair dados
|
|
print(f"\n Extraindo dados dos dias com registros...")
|
|
all_extracted_data = []
|
|
|
|
for dia, qtd in detalhes_dias:
|
|
print(f"\n Processando {dia} ({qtd:,} registros esperados)")
|
|
processar_dia_chaves(session, dia, all_extracted_data)
|
|
|
|
print(f"\n Total extraído (após filtro CNPJ): {len(all_extracted_data):,} registros")
|
|
|
|
if not all_extracted_data:
|
|
print(" Nenhum dado extraído após filtro de CNPJ.")
|
|
return []
|
|
|
|
# Verificar duplicatas
|
|
print(f"\n Verificando duplicatas no banco...")
|
|
chaves_extraidas = [r['chave'] for r in all_extracted_data if r['chave']]
|
|
chaves_existentes = consultar_chaves_existentes(chaves_extraidas)
|
|
|
|
# Filtrar registros novos
|
|
registros_novos = [r for r in all_extracted_data if r['chave'] not in chaves_existentes]
|
|
|
|
print(f" Registros duplicados: {len(all_extracted_data) - len(registros_novos):,}")
|
|
print(f" Registros novos: {len(registros_novos):,}")
|
|
|
|
if not registros_novos:
|
|
print(" Todos os registros já existem no banco.")
|
|
return []
|
|
|
|
# Inserir no banco fato_notas_entrada
|
|
print(f"\n Inserindo {len(registros_novos):,} notas no banco...")
|
|
inseridos = inserir_nfes_banco(registros_novos)
|
|
print(f" {inseridos:,} notas inseridas com sucesso!")
|
|
|
|
# Retornar lista de chaves novas
|
|
chaves_novas = [r['chave'] for r in registros_novos if r['chave']]
|
|
return chaves_novas
|
|
|
|
# =============================================================================
|
|
# PARTE 2: BUSCAR ITENS DAS NOTAS NOVAS
|
|
# =============================================================================
|
|
|
|
def buscar_valor_xml(item_element, campo):
|
|
"""Busca um valor específico dentro de um elemento XML do item da NFe"""
|
|
try:
|
|
prod = item_element.find('prod')
|
|
imposto = item_element.find('imposto')
|
|
|
|
if campo in ['cProd', 'cEAN', 'xProd', 'NCM', 'CEST', 'CFOP', 'uCom', 'qCom', 'vUnCom', 'vProd', 'vFrete', 'vSeg', 'vDesc', 'vOutro', 'xPed']:
|
|
if prod is not None:
|
|
elem = prod.find(campo)
|
|
if elem is not None and elem.text:
|
|
valor = elem.text.strip()
|
|
if campo == 'qCom':
|
|
try:
|
|
valor_float = float(valor)
|
|
if valor_float == int(valor_float):
|
|
valor = str(int(valor_float))
|
|
else:
|
|
valor = str(valor_float).rstrip('0').rstrip('.')
|
|
except:
|
|
pass
|
|
return valor
|
|
return ''
|
|
|
|
elif campo in ['orig', 'CST', 'modBC', 'vBC', 'pICMS', 'vICMS', 'vBCFCP', 'pFCP', 'vFCP', 'modBCST', 'pMVAST', 'vBCST', 'pICMSST', 'vICMSST', 'vBCFCPST', 'pFCPST', 'vFCPST', 'vICMSDeson']:
|
|
if imposto is not None:
|
|
icms = imposto.find('ICMS')
|
|
if icms is not None:
|
|
for icms_child in icms:
|
|
elem = icms_child.find(campo)
|
|
if elem is not None and elem.text:
|
|
valor = elem.text.strip()
|
|
if campo in ['pICMS', 'pICMSST', 'pFCP', 'pFCPST', 'pMVAST']:
|
|
try:
|
|
valor_float = float(valor)
|
|
valor = str(round(valor_float / 100, 4))
|
|
except:
|
|
pass
|
|
return valor
|
|
return ''
|
|
|
|
elif campo == 'vII':
|
|
if imposto is not None:
|
|
ii = imposto.find('II')
|
|
if ii is not None:
|
|
elem = ii.find('vII')
|
|
if elem is not None and elem.text:
|
|
return elem.text.strip()
|
|
return ''
|
|
|
|
elif campo == 'vIPI':
|
|
if imposto is not None:
|
|
ipi = imposto.find('IPI')
|
|
if ipi is not None:
|
|
for ipi_child in ipi:
|
|
elem = ipi_child.find('vIPI')
|
|
if elem is not None and elem.text:
|
|
return elem.text.strip()
|
|
return ''
|
|
|
|
return ''
|
|
except:
|
|
return ''
|
|
|
|
def inserir_itens_banco(lista_itens):
|
|
"""Insere os itens da NFe no banco de dados"""
|
|
if not lista_itens:
|
|
return 0
|
|
|
|
try:
|
|
conn = get_db_connection()
|
|
cursor = conn.cursor()
|
|
|
|
try:
|
|
cursor.execute("SELECT TOP 0 * FROM [GINSENG].[dbo].[fato_notas_entrada_itens]")
|
|
colunas_tabela = [desc[0].lower() for desc in cursor.description]
|
|
colunas_tabela = [col for col in colunas_tabela if col not in ['id']]
|
|
except Exception as e:
|
|
print(f" Erro ao verificar estrutura da tabela: {e}")
|
|
colunas_tabela = ['chave', 'n_item', 'cod_produto', 'produto', 'quantidade', 'valor_unitario', 'valor_total_produtos']
|
|
|
|
campos_mapeados = {
|
|
'chave': 'chave',
|
|
'n_item': 'n_item',
|
|
'data_emissao': 'data_emissao',
|
|
'cod_produto': 'cod_produto',
|
|
'produto': 'produto',
|
|
'cEAN': 'cean',
|
|
'NCM': 'ncm',
|
|
'CEST': 'cest',
|
|
'CFOP': 'cfop',
|
|
'unidade_medida': 'unidade_medida',
|
|
'quantidade': 'quantidade',
|
|
'valor_unitario': 'valor_unitario',
|
|
'valor_total_produtos': 'valor_total_produtos',
|
|
'valor_frete': 'valor_frete',
|
|
'valor_seguro': 'valor_seguro',
|
|
'valor_desconto': 'valor_desconto',
|
|
'valor_outras_despesas': 'valor_outras_despesas',
|
|
'codigo_pedido': 'codigo_pedido',
|
|
'cod_origem': 'cod_origem',
|
|
'CST': 'cst',
|
|
'modalidade_BC_ICMS': 'modalidade_bc_icms',
|
|
'valor_BC_ICMS': 'valor_bc_icms',
|
|
'aliquota_ICMS': 'aliquota_icms',
|
|
'valor_ICMS': 'valor_icms',
|
|
'valor_BC_FCP': 'valor_bc_fcp',
|
|
'aliquota_FCP': 'aliquota_fcp',
|
|
'valor_FCP': 'valor_fcp',
|
|
'modalidade_BC_ST': 'modalidade_bc_st',
|
|
'aliquota_MVA_ST': 'aliquota_mva_st',
|
|
'valor_BC_ST': 'valor_bc_st',
|
|
'aliquota_ICMS_ST': 'aliquota_icms_st',
|
|
'valor_ICMSST': 'valor_icmsst',
|
|
'valor_BC_FCPST': 'valor_bc_fcpst',
|
|
'aliquota_FCPST': 'aliquota_fcpst',
|
|
'valor_FCPST': 'valor_fcpst',
|
|
'valor_II': 'valor_ii',
|
|
'valor_IPI': 'valor_ipi',
|
|
'valor_ICMS_desonerado': 'valor_icms_desonerado'
|
|
}
|
|
|
|
campos_validos = []
|
|
valores_placeholders = []
|
|
|
|
for campo_item, campo_tabela in campos_mapeados.items():
|
|
if campo_tabela in colunas_tabela:
|
|
campos_validos.append(f"[{campo_tabela}]")
|
|
valores_placeholders.append("?")
|
|
|
|
if not campos_validos:
|
|
print(" Nenhum campo válido encontrado para inserção!")
|
|
return 0
|
|
|
|
insert_query = f"""
|
|
INSERT INTO [GINSENG].[dbo].[fato_notas_entrada_itens] ({', '.join(campos_validos)})
|
|
VALUES ({', '.join(valores_placeholders)})
|
|
"""
|
|
|
|
registros_inseridos = 0
|
|
for item in lista_itens:
|
|
try:
|
|
valores = []
|
|
for campo_item, campo_tabela in campos_mapeados.items():
|
|
if campo_tabela in colunas_tabela:
|
|
valor = item.get(campo_item, '')
|
|
if valor == '':
|
|
valor = None
|
|
valores.append(valor)
|
|
|
|
cursor.execute(insert_query, valores)
|
|
registros_inseridos += 1
|
|
except Exception as e:
|
|
print(f" Erro ao inserir item {item.get('n_item', '')}: {e}")
|
|
|
|
conn.commit()
|
|
cursor.close()
|
|
conn.close()
|
|
|
|
return registros_inseridos
|
|
|
|
except Exception as e:
|
|
print(f" Erro ao conectar com banco de dados: {e}")
|
|
return 0
|
|
|
|
def buscar_itens_das_chaves(chaves_novas):
|
|
"""
|
|
PARTE 2: Busca os itens das notas novas via API
|
|
"""
|
|
print("\n" + "=" * 70)
|
|
print(" PARTE 2: BUSCANDO ITENS DAS NOTAS NOVAS")
|
|
print("=" * 70)
|
|
|
|
if not chaves_novas:
|
|
print(" Nenhuma chave nova para processar.")
|
|
return 0
|
|
|
|
print(f" Total de chaves para buscar itens: {len(chaves_novas):,}")
|
|
|
|
session = create_session_with_retry()
|
|
lista_itens_notas = []
|
|
chaves_processadas = 0
|
|
chaves_com_erro = 0
|
|
|
|
for i, chave in enumerate(chaves_novas, 1):
|
|
print(f"\n [{i}/{len(chaves_novas)}] Processando: {chave[:20]}...")
|
|
|
|
url = f"{API_URL}?access_key[]={chave}"
|
|
|
|
try:
|
|
response = fazer_requisicao_robusta(session, url, API_HEADERS, None, max_tentativas=3)
|
|
|
|
if response and response.status_code == 200:
|
|
data = response.json()
|
|
nfe_list = data.get('data', [])
|
|
|
|
if not nfe_list:
|
|
print(f" Nenhum dado retornado")
|
|
continue
|
|
|
|
for nfe_data in nfe_list:
|
|
chave_retornada = nfe_data.get('access_key', '')
|
|
|
|
xml_base64 = nfe_data.get('xml', '')
|
|
if not xml_base64 or not isinstance(xml_base64, str):
|
|
print(f" XML não encontrado")
|
|
continue
|
|
|
|
try:
|
|
xml_decoded = base64.b64decode(xml_base64).decode('utf-8')
|
|
xml_decoded = xml_decoded.replace('xmlns="http://www.portalfiscal.inf.br/nfe"', '')
|
|
root = ET.fromstring(xml_decoded)
|
|
except Exception as e:
|
|
print(f" Erro ao decodificar XML: {e}")
|
|
continue
|
|
|
|
# Buscar data de emissão
|
|
data_emissao = ''
|
|
dhEmi_elem = root.find('.//ide/dhEmi')
|
|
if dhEmi_elem is not None and dhEmi_elem.text:
|
|
data_emissao = dhEmi_elem.text[:10]
|
|
|
|
# Buscar todos os itens
|
|
itens = root.findall('.//det')
|
|
print(f" {len(itens)} itens encontrados (Emissão: {data_emissao})")
|
|
|
|
for j, item in enumerate(itens, 1):
|
|
n_item = item.get('nItem', str(j))
|
|
item_data = {
|
|
'chave': chave_retornada,
|
|
'n_item': n_item,
|
|
'data_emissao': data_emissao,
|
|
'cod_produto': buscar_valor_xml(item, 'cProd'),
|
|
'produto': buscar_valor_xml(item, 'xProd'),
|
|
'cEAN': buscar_valor_xml(item, 'cEAN'),
|
|
'NCM': buscar_valor_xml(item, 'NCM'),
|
|
'CEST': buscar_valor_xml(item, 'CEST'),
|
|
'CFOP': buscar_valor_xml(item, 'CFOP'),
|
|
'unidade_medida': buscar_valor_xml(item, 'uCom'),
|
|
'quantidade': buscar_valor_xml(item, 'qCom'),
|
|
'valor_unitario': buscar_valor_xml(item, 'vUnCom'),
|
|
'valor_total_produtos': buscar_valor_xml(item, 'vProd'),
|
|
'valor_frete': buscar_valor_xml(item, 'vFrete'),
|
|
'valor_seguro': buscar_valor_xml(item, 'vSeg'),
|
|
'valor_desconto': buscar_valor_xml(item, 'vDesc'),
|
|
'valor_outras_despesas': buscar_valor_xml(item, 'vOutro'),
|
|
'codigo_pedido': buscar_valor_xml(item, 'xPed'),
|
|
'cod_origem': buscar_valor_xml(item, 'orig'),
|
|
'CST': buscar_valor_xml(item, 'CST'),
|
|
'modalidade_BC_ICMS': buscar_valor_xml(item, 'modBC'),
|
|
'valor_BC_ICMS': buscar_valor_xml(item, 'vBC'),
|
|
'aliquota_ICMS': buscar_valor_xml(item, 'pICMS'),
|
|
'valor_ICMS': buscar_valor_xml(item, 'vICMS'),
|
|
'valor_BC_FCP': buscar_valor_xml(item, 'vBCFCP'),
|
|
'aliquota_FCP': buscar_valor_xml(item, 'pFCP'),
|
|
'valor_FCP': buscar_valor_xml(item, 'vFCP'),
|
|
'modalidade_BC_ST': buscar_valor_xml(item, 'modBCST'),
|
|
'aliquota_MVA_ST': buscar_valor_xml(item, 'pMVAST'),
|
|
'valor_BC_ST': buscar_valor_xml(item, 'vBCST'),
|
|
'aliquota_ICMS_ST': buscar_valor_xml(item, 'pICMSST'),
|
|
'valor_ICMSST': buscar_valor_xml(item, 'vICMSST'),
|
|
'valor_BC_FCPST': buscar_valor_xml(item, 'vBCFCPST'),
|
|
'aliquota_FCPST': buscar_valor_xml(item, 'pFCPST'),
|
|
'valor_FCPST': buscar_valor_xml(item, 'vFCPST'),
|
|
'valor_II': buscar_valor_xml(item, 'vII'),
|
|
'valor_IPI': buscar_valor_xml(item, 'vIPI'),
|
|
'valor_ICMS_desonerado': buscar_valor_xml(item, 'vICMSDeson')
|
|
}
|
|
|
|
lista_itens_notas.append(item_data)
|
|
|
|
chaves_processadas += 1
|
|
|
|
else:
|
|
print(f" Erro na requisição")
|
|
chaves_com_erro += 1
|
|
|
|
except Exception as e:
|
|
print(f" Erro: {e}")
|
|
chaves_com_erro += 1
|
|
|
|
print(f"\n Chaves processadas: {chaves_processadas:,}")
|
|
print(f" Chaves com erro: {chaves_com_erro:,}")
|
|
print(f" Total de itens extraídos: {len(lista_itens_notas):,}")
|
|
|
|
# Inserir itens no banco
|
|
if lista_itens_notas:
|
|
print(f"\n Inserindo {len(lista_itens_notas):,} itens no banco...")
|
|
inseridos = inserir_itens_banco(lista_itens_notas)
|
|
print(f" {inseridos:,} itens inseridos com sucesso!")
|
|
return inseridos
|
|
|
|
return 0
|
|
|
|
# =============================================================================
|
|
# ENVIAR STATUS PARA API
|
|
# =============================================================================
|
|
|
|
def enviar_status_api(sucesso):
|
|
"""Envia status para a API de monitoramento"""
|
|
try:
|
|
print("\n" + "=" * 70)
|
|
print(" ENVIANDO STATUS PARA API DE MONITORAMENTO")
|
|
print("=" * 70)
|
|
|
|
url = "https://api.grupoginseng.com.br/api/status/4"
|
|
sao_paulo_offset = timedelta(hours=-3)
|
|
current_datetime = datetime.now(timezone(sao_paulo_offset)).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
status_code = "OK" if sucesso else "FAIL"
|
|
|
|
payload = {
|
|
"STATUS": status_code,
|
|
"DATA": current_datetime
|
|
}
|
|
|
|
headers = {"Content-Type": "application/json"}
|
|
|
|
response = requests.put(url, json=payload, headers=headers)
|
|
|
|
print(f" Status: {status_code}")
|
|
print(f" Hora: {current_datetime}")
|
|
print(f" Response: {response.status_code}")
|
|
|
|
except Exception as e:
|
|
print(f" Erro ao enviar status: {e}")
|
|
|
|
# =============================================================================
|
|
# EXECUÇÃO PRINCIPAL
|
|
# =============================================================================
|
|
|
|
def main():
|
|
print("\n" + "=" * 70)
|
|
print(" SCRIPT COMPLETO - NOTAS E ITENS")
|
|
print(" Data de execução:", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
|
print("=" * 70)
|
|
|
|
sucesso = True
|
|
|
|
try:
|
|
# PARTE 1: Buscar notas e identificar chaves novas
|
|
chaves_novas = buscar_notas_e_identificar_novas()
|
|
|
|
# PARTE 2: Buscar itens das notas novas
|
|
if chaves_novas:
|
|
itens_inseridos = buscar_itens_das_chaves(chaves_novas)
|
|
else:
|
|
print("\n Nenhuma chave nova encontrada. Pulando busca de itens.")
|
|
itens_inseridos = 0
|
|
|
|
# Resumo final
|
|
print("\n" + "=" * 70)
|
|
print(" RESUMO FINAL")
|
|
print("=" * 70)
|
|
print(f" Notas novas encontradas: {len(chaves_novas):,}")
|
|
print(f" Itens inseridos: {itens_inseridos:,}")
|
|
|
|
except Exception as e:
|
|
print(f"\n ERRO GERAL: {e}")
|
|
sucesso = False
|
|
|
|
# Enviar status
|
|
enviar_status_api(sucesso)
|
|
|
|
print("\n" + "=" * 70)
|
|
print(" PROCESSAMENTO CONCLUÍDO!")
|
|
print("=" * 70)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|