Я пытаюсь создать запросчик NFE для каждого сертификата, но что бы я ни делал, я всегда получаю эту ошибку, я уже ждал 1Python

Программы на Python
Anonymous
 Я пытаюсь создать запросчик NFE для каждого сертификата, но что бы я ни делал, я всегда получаю эту ошибку, я уже ждал 1

Сообщение Anonymous »

Я использую Python 3.8, использую платформу odoo 14, сертификат в порядке, соединение с SEFAZ в порядке, XML-файл SOAP в порядке, независимо от того, как долго я жду, он всегда выдает эту ошибку: Ошибка 656 - Неправильное использование.
Вот выдаваемая ошибка:
2025-10-29 11:32:33,700 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: URL: https://www1.nfe.fazenda.gov.br/NFeDist ... aoDFe.asmx
29 октября 2025 11:32:33,700 7896 ИНФО daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: CNPJ: 33333480000161
2025-10-29 11:32:33,700 7896 ИНФО daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: UltNSU: 000000000000000
2025-10-29 11:32:33,974 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: Код состояния: 200
2025-10-29 11:32:33,974 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: ответ SEFAZ: b'soap:Body11.7.6656 Отклонение: неправильное потребление (в последующих запросах необходимо использовать UltNSU. Попробуйте после 1 time)2025-10-29T08:32:34-03:00000000000005184000000000000000'...
2025-10-29 11:32:33,974 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: cStat: 656, xReason: Отклонение: неправильное потребление (в последующих запросах необходимо использовать UltNSU. Попробуйте через 1 час)
2025-10-29 11:32:33,974 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: UltNSU: 000000000005184, MaxNSU: 000000000000000
2025-10-29 11:32:33,974 7896 ПРЕДУПРЕЖДЕНИЕ daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: Ошибка 656 - Неправильный расход. UltNSU: 000000000000000, MaxNSU: 000000000000000
2025-10-29 11:32:33,975 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: MaxNSU = 0 - В языке нет документов SEFAZ для этого CNPJ
29 октября 2025 11:32:33,975 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: Поиск завершен. Найдено 0 XML(ов).
2025-10-29 11:32:33,975 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: УВЕДОМЛЕНИЕ: Информация — поиск завершен. Найдено 0 XML(ов).
29.10.2025 11:32:33,975 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: 🎯 Обработка завершена: в дереве создано 0 новых NF-e
29.10.2025 11:32:33,975 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: УВЕДОМЛЕНИЕ: Информация - 🎯 Обработка завершена: в дереве создано 0 новых NF-e
29 октября 2025 11:32:33,975 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: В SEFAZ не найдено новых счетов.
2025-10-29 11:32:33,975 7896 INFO daikkodev3 odoo.addons.odoo_nfe.models.nfe_received: УВЕДОМЛЕНИЕ: Поиск завершен — в SEFAZ не найдено новых счетов.
2025-10-29 11:32:33,977 7896 INFO daikkodev3 werkzeug: 127.0.0.1 - - [29/октябрь/2025 11:32:33] "POST /web/action/run HTTP/1.1" 200 - 24 0,017 0,868
вот использованные методы:
def fetch_all_nfe_from_sefaz(self, limit=50):
company = self.env.company
cnpj_dest = company.cnpj_cpf
if not cnpj_dest:
raise UserError("CNPJ da empresa (campo cnpj_cpf) não configurado!")

Param = self.env['ir.config_parameter'].sudo()
ult_nsu_str = Param.get_param('nfe.received.ultimo_nsu', '0')

# SE O NSU ATUAL É MAIOR QUE 0 E ESTÁ DANDO ERRO, RESETAR PARA 0
if int(ult_nsu_str) > 1000: # Se NSU está muito alto
self._log_to_notification('warning',
f"NSU atual ({ult_nsu_str}) parece estar inconsistente. Resetando para 0.")
ult_nsu_str = '0'
Param.set_param('nfe.received.ultimo_nsu', '0')

ult_nsu_int = int(ult_nsu_str) if ult_nsu_str.isdigit() else 0
ult_nsu = str(ult_nsu_int).zfill(15)

self._log_to_notification('info', f"Iniciando busca de NF-e. NSU inicial: {ult_nsu}")

client = SefazClientDistribuicao(company)
total_xmls = []

try:
result = client.fetch_nfe(cnpj_dest, ult_nsu)
xmls = result.get('xmls', [])
novo_ult_nsu = result.get('ult_nsu', ult_nsu).zfill(15)
max_nsu = result.get('max_nsu', '000000000000000')
cstat = result.get('cstat', '000')

# SE É ERRO 656 COM MAXNSU = 0, TRATAR COMO SUCESSO SEM DOCUMENTOS
if cstat == '656' and max_nsu == '000000000000000':
self._log_to_notification('info',
"✅ Conexão SEFAZ OK! Nenhuma nota disponível no momento.")

# Manter o NSU atual (não incrementar)
return 0

# Se tem documentos, processar
total_xmls.extend(xmls)

# Atualizar NSU apenas se for maior que o atual
if int(novo_ult_nsu) > int(ult_nsu):
Param.set_param('nfe.received.ultimo_nsu', novo_ult_nsu)
self._log_to_notification('info', f"📈 NSU atualizado: {ult_nsu} → {novo_ult_nsu}")

self._log_to_notification('info',
f"Busca concluída. {len(total_xmls)} XML(s) encontrado(s).")

return self._processar_xmls(total_xmls, cnpj_dest)

except UserError as e:
error_str = str(e)
if '656' in error_str and 'MaxNSU = 0' not in error_str:
self._log_to_notification('warning',
"SEFAZ bloqueou requisições (erro 656). Aguardando 1 hora.")
from datetime import datetime
Param.set_param('nfe.received.ultimo_erro_656', datetime.now().isoformat())
return 0
else:
self._log_to_notification('error', f"Erro SEFAZ: {error_str}")
return 0
except Exception as e:
self._log_to_notification('error', f"Erro inesperado: {str(e)}")
return 0

def _fetch_nfe(self, use_param=True):
company = self.env.company
cnpj_dest = company.cnpj_cpf
if not cnpj_dest:
raise UserError("CNPJ da empresa não configurado!")

Param = self.env['ir.config_parameter'].sudo()
ult_nsu_str = Param.get_param('nfe.received.ultimo_nsu', '0')
ult_nsu_int = int(ult_nsu_str) if ult_nsu_str.isdigit() else 0
ult_nsu = str(ult_nsu_int).zfill(15)

self._log_to_notification('info', f"Iniciando busca NF-e. Último NSU: {ult_nsu}")

client = SefazClientDistribuicao(company)
total_xmls = []
empty_count = 0
attempt = 0
max_attempts = 5

while attempt < max_attempts:
attempt += 1
try:
result = client.fetch_nfe(cnpj_dest, ult_nsu)
xmls = result.get('xmls', [])
novo_ult_nsu = result.get('ult_nsu', ult_nsu).zfill(15)

# SEMPRE atualizar o NSU, mesmo que não tenha XMLs
if use_param and novo_ult_nsu != ult_nsu:
Param.set_param('nfe.received.ultimo_nsu', novo_ult_nsu)
self._log_to_notification('info', f"📈 NSU atualizado: {ult_nsu} → {novo_ult_nsu}")
elif use_param and novo_ult_nsu == ult_nsu and len(xmls) == 0:
# Se não tem XMLs e o NSU não mudou, incrementar manualmente para evitar loop
novo_nsu_incrementado = str(int(ult_nsu) + 1).zfill(15)
Param.set_param('nfe.received.ultimo_nsu', novo_nsu_incrementado)
self._log_to_notification('info',
f"📈 NSU incrementado (sem mudança): {ult_nsu} → {novo_nsu_incrementado}")
ult_nsu = novo_nsu_incrementado
continue

if not xmls and company.nfe_environment == '2':
self._log_to_notification('info', "Nenhuma NF-e real em homologação, gerando mocks.")
return self.fetch_nfe_mock(limit=3)

total_xmls.extend(xmls)

if not xmls:
empty_count += 1
if empty_count >= 2:
self._log_to_notification('info', "Sem novas NF-es. Interrompendo.")
break
else:
empty_count = 0

if novo_ult_nsu == ult_nsu:
break

ult_nsu = novo_ult_nsu
time.sleep(1)

except UserError as e:
error_str = str(e)
if '656' in error_str:
self._log_to_notification('info', "Nenhum documento localizado na SEFAZ (erro 656).")
# Para erro 656, SEMPRE incrementar o NSU
novo_nsu = str(int(ult_nsu) + 1).zfill(15)
if use_param:
Param.set_param('nfe.received.ultimo_nsu', novo_nsu)
self._log_to_notification('info', f"📈 NSU incrementado após erro 656: {ult_nsu} → {novo_nsu}")

if company.nfe_environment == '2':
self._log_to_notification('info', "Em homologação, gerando notas fictícias para teste.")
return self.fetch_nfe_mock(limit=3)
else:
# Em produção, continuar com o novo NSU
ult_nsu = novo_nsu
if attempt < max_attempts:
time.sleep(2)
continue
else:
self._log_to_notification('info', "Múltiplos erros 656 consecutivos. Interrompendo busca.")
break
elif '589' in error_str and company.nfe_environment == '2':
self._log_to_notification('info', "NSU maior que disponível em homologação. Gerando mocks.")
return self.fetch_nfe_mock(limit=3)
else:
self._log_to_notification('warning', f"Erro SEFAZ: {e}")
break
except Exception as e:
self._log_to_notification('error', f"Erro inesperado ao buscar NF-e: {str(e)}")
break

return self._processar_xmls(total_xmls, cnpj_dest)

Класс подключения SEFAZ
class SefazClientDistribuicao:
NAMESPACE_SOAP = "http://www.w3.org/2003/05/soap-envelope"
NAMESPACE_SERVICO = "http://www.portalfiscal.inf.br/nfe/wsdl ... ibuicaoDFe"

SERVICE_URLS = {
'1': 'https://www1.nfe.fazenda.gov.br/NFeDist ... aoDFe.asmx',
'2': 'https://hom1.nfe.fazenda.gov.br/NFeDist ... aoDFe.asmx',
}

def __init__(self, company):
self.company = company
cert_obj = company.certificate_ecnpj_id
if not cert_obj or not cert_obj.file or not cert_obj.password:
raise UserError("Certificado digital não configurado! (company.certificate_ecnpj_id)")
self.pfx_data = base64.b64decode(cert_obj.file)
self.pfx_password = cert_obj.password.strip()
self.environment = company.nfe_environment or '2'
self.uf_code = company.state_id.ibge_code or 35

@contextmanager
def _temp_cert_files(self):
private_key, certificate, _ = load_key_and_certificates(self.pfx_data, self.pfx_password.encode())
if not private_key or not certificate:
raise UserError("Falha ao carregar certificado digital.")

with tempfile.NamedTemporaryFile(delete=False, suffix=".pem") as cert_file, \
tempfile.NamedTemporaryFile(delete=False, suffix=".pem") as key_file:

cert_file.write(certificate.public_bytes(Encoding.PEM))
key_file.write(private_key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
cert_file.flush()
key_file.flush()

try:
yield cert_file.name, key_file.name
finally:
for f in [cert_file.name, key_file.name]:
try:
os.unlink(f)
except Exception:
pass

def fetch_nfe(self, cnpj_destinatario, ultNSU='0'):
with self._temp_cert_files() as (cert_path, key_path):
session = Session()
session.cert = (cert_path, key_path)
session.verify = True

cnpj_dest_sem_mask = ''.join(filter(str.isdigit, cnpj_destinatario))
ult_nsu_formatado = str(ultNSU).zfill(15)

# XML oficial - VERSÃO SIMPLIFICADA QUE FUNCIONA
dist_dfe_xml = f'''
{self.environment}
{self.uf_code}
{cnpj_dest_sem_mask}

{ult_nsu_formatado}

'''

soap_xml = f'''




{dist_dfe_xml}



'''

headers = {
'Content-Type': 'application/soap+xml; charset=utf-8',
'SOAPAction': 'http://www.portalfiscal.inf.br/nfe/wsdl ... eInteresse'
}

service_url = self.SERVICE_URLS[self.environment]

_logger.info(f"=== REQUISIÇÃO SEFAZ ===")
_logger.info(f"URL: {service_url}")
_logger.info(f"CNPJ: {cnpj_dest_sem_mask}")
_logger.info(f"UltNSU: {ult_nsu_formatado}")

try:
response = session.post(service_url, data=soap_xml.encode('utf-8'), headers=headers, timeout=60)
_logger.info(f"Status Code: {response.status_code}")
response.raise_for_status()

return self._process_sefaz_response(response, ult_nsu_formatado)

except Exception as e:
_logger.error(f"Erro SEFAZ: {e}")
raise UserError(f"Falha na SEFAZ: {str(e)}")

# ESTES MÉTODOS PRECISAM ESTAR FORA DO fetch_nfe, NO MESMO NÍVEL
def _create_sefaz_soap_xml_alternative(self, cnpj, ult_nsu):
"""Cria XML SOAP alternativo baseado na documentação oficial"""
dist_dfe_xml = f'''
{self.environment}
{self.uf_code}
{cnpj}

{ult_nsu}

'''

# Versão alternativa sem namespaces extras
soap_xml = f'''




{dist_dfe_xml}



'''

_logger.info("Tentando com XML SOAP alternativo (sem namespaces extras)...")
return soap_xml

def _process_sefaz_response(self, response, ult_nsu_formatado):
"""Processa resposta da SEFAZ - VERSÃO CORRIGIDA PARA MAXNSU = 0"""
try:
content = response.content
_logger.info(f"Resposta SEFAZ: {content[:1000]}...")

# Parse do XML
root = etree.fromstring(content)

# Encontrar retDistDFeInt - CORREÇÃO DO WARNING
ret_element = None
namespaces = [
'{http://www.portalfiscal.inf.br/nfe}retDistDFeInt',
'retDistDFeInt'
]

for ns in namespaces:
ret_element = root.find(f'.//{ns}')
if ret_element is not None:
break

if ret_element is None:
# Tentar encontrar sem namespace
for elem in root.iter():
if 'retDistDFeInt' in elem.tag:
ret_element = elem
break

if ret_element is None:
raise UserError("Resposta não contém retDistDFeInt")

# Extrair dados importantes
cstat_elem = ret_element.find('.//{http://www.portalfiscal.inf.br/nfe}cStat')
if cstat_elem is None:
cstat_elem = ret_element.find('.//cStat')
cstat = cstat_elem.text if cstat_elem is not None else '000'

xmotivo_elem = ret_element.find('.//{http://www.portalfiscal.inf.br/nfe}xMotivo')
if xmotivo_elem is None:
xmotivo_elem = ret_element.find('.//xMotivo')
xmotivo = xmotivo_elem.text if xmotivo_elem is not None else ''

ult_nsu_elem = ret_element.find('.//{http://www.portalfiscal.inf.br/nfe}ultNSU')
if ult_nsu_elem is None:
ult_nsu_elem = ret_element.find('.//ultNSU')
ult_nsu = ult_nsu_elem.text if ult_nsu_elem is not None else ult_nsu_formatado

max_nsu_elem = ret_element.find('.//{http://www.portalfiscal.inf.br/nfe}maxNSU')
if max_nsu_elem is None:
max_nsu_elem = ret_element.find('.//maxNSU')
max_nsu = max_nsu_elem.text if max_nsu_elem is not None else '000000000000000'

_logger.info(f"cStat: {cstat}, xMotivo: {xmotivo}")
_logger.info(f"UltNSU: {ult_nsu}, MaxNSU: {max_nsu}")

# TRATAMENTO ESPECÍFICO PARA ERRO 656 COM MAXNSU = 0
if cstat == '656':
_logger.warning(f"Erro 656 - Consumo indevido. UltNSU: {ult_nsu_formatado}, MaxNSU: {max_nsu}")

# SE MAXNSU É 0, SIGNIFICA QUE NÃO HÁ DOCUMENTOS DISPONÍVEIS
if max_nsu == '000000000000000':
_logger.info("MaxNSU = 0 - Nenhum documento disponível na SEFAZ para este CNPJ")
# Retornar sucesso mas sem documentos
return {
'xmls': [],
'ult_nsu': '0', # Resetar para 0
'max_nsu': '0',
'cstat': '656',
'xmotivo': xmotivo
}
else:
# Se tem MaxNSU disponível, sugerir usar ele
raise UserError(f"Erro 656 - Use NSU ≤ {max_nsu}")

# Outros erros
elif cstat not in ('137', '138', '139'):
raise UserError(f"SEFAZ retornou erro: {cstat} - {xmotivo}")

# Extrair documentos
xmls = []
doc_zips = ret_element.findall('.//{http://www.portalfiscal.inf.br/nfe}docZip')
if not doc_zips:
doc_zips = ret_element.findall('.//docZip')

for doc_zip in doc_zips:
if doc_zip.text:
try:
zip_data = base64.b64decode(doc_zip.text)
with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
for filename in zf.namelist():
if filename.endswith('.xml'):
xml_content = zf.read(filename).decode('utf-8')
xmls.append(xml_content)
_logger.info(f"XML extraído: {filename}")
except zipfile.BadZipFile:
try:
xml_content = base64.b64decode(doc_zip.text).decode('utf-8')
if 'nfe' in xml_content.lower():
xmls.append(xml_content)
_logger.info("XML processado (formato direto)")
except Exception as e:
_logger.warning(f"Erro ao processar documento: {e}")
except Exception as e:
_logger.warning(f"Erro ao extrair ZIP: {e}")

_logger.info(f"Total de XMLs: {len(xmls)}")
return {
'xmls': xmls,
'ult_nsu': ult_nsu,
'max_nsu': max_nsu,
'cstat': cstat,
'xmotivo': xmotivo
}

except UserError:
raise
except Exception as e:
_logger.error(f"Erro ao processar resposta: {e}")
raise UserError(f"Erro no processamento: {str(e)}")

def _extract_text(self, element, tags):
"""Extrai texto de elemento com múltiplas possibilidades de tag"""
for tag in tags:
elem = element.find(f'.//{tag}')
if elem is not None and elem.text:
return elem.text.strip()
return ''

def _extract_xml_from_doc_zip(self, doc_zip_text):
"""Extrai XMLs do docZip"""
xmls = []
try:
zip_bytes = base64.b64decode(doc_zip_text)
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
for name in zf.namelist():
if name.endswith('.xml'):
xml_content = zf.read(name).decode('utf-8')
xmls.append(xml_content)
_logger.info(f"XML processado: {name}")
except zipfile.BadZipFile:
try:
xml_content = base64.b64decode(doc_zip_text).decode('utf-8')
if any(tag in xml_content for tag in ['nfeProc', '

Подробнее здесь: https://stackoverflow.com/questions/798 ... -importa-o

Вернуться в «Python»