Como Automatizar a Consolidação de Faturas de Compra no ERPNext com Python

Como Automatizar a Consolidação de Faturas de Compra no ERPNext com Python

Gerenciar um alto volume de faturas de compra (Purchase Invoices) de forma individual no ERPNext pode gerar gargalos operacionais significativos no setor financeiro e de compras. Quando múltiplos pedidos de um mesmo fornecedor chegam em lotes, processar, validar e reconciliar cada documento manualmente aumenta a probabilidade de divergências contábeis e atrasos em conciliações.

Neste artigo, vamos explorar como projetar e implementar um fluxo de trabalho automatizado e repetível no Frappe Framework (o motor do ERPNext) utilizando Python para selecionar, agrupar e consolidar múltiplas faturas de compra com segurança transacional.


O Desafio Técnico: Agrupamento e Integridade no ERPNext

No ERPNext, uma fatura de compra possui vínculos estritos com o plano de contas, impostos, retenções e lançamentos no razão geral (General Ledger). Ao consolidar múltiplas faturas em um fluxo unificado (como a geração de um Payment Entry em lote ou uma fatura mãe consolidada), precisamos garantir:

  1. Consistência de Fornecedor e Moeda: As faturas selecionadas devem pertencer ao mesmo fornecedor (Supplier) e compartilhar a mesma moeda de transação.
  2. Status de Pagamento: Filtrar apenas faturas com status Unpaid ou Partly Paid, ignorando rascunhos ou canceladas.
  3. Tratamento de Impostos e Descontos: Garantir que as linhas de impostos (Purchase Taxes and Charges) e centros de custo sejam respeitados sem duplicação contábil.
  4. Atomicidade: Todas as atualizações devem ocorrer dentro de uma transação de banco de dados para evitar estados intermediários em caso de falha.

Arquitetura da Solução em Python

A abordagem recomendada no ecossistema Frappe consiste em criar um Server Script ou um método de API em um aplicativo customizado que recebe uma lista de IDs de faturas (docnames), valida os pré-requisitos e executa o processamento em lote.

Implementação do Método de Consolidação

Abaixo está um exemplo de implementação estruturada utilizando a ORM do Frappe:

python
import frappe
from frappe import _

@frappe.whitelist()
def consolidatepurchaseinvoices(invoicenames):
“””
Consolida múltiplas faturas de compra em um único fluxo de pagamento/reconciliação.
“””
if not invoice
names:
frappe.throw(_(“Nenhuma fatura foi selecionada.”))

# Garante que invoice_names seja uma lista
if isinstance(invoice_names, str):
    import json
    invoice_names = json.loads(invoice_names)

# Consulta em lote para validação prévia de integridade
invoices = frappe.get_all(
    "Purchase Invoice",
    filters={"name": ["in", invoice_names], "docstatus": 1},
    fields=["name", "supplier", "currency", "outstanding_amount", "company"]
)

if len(invoices) != len(invoice_names):
    frappe.throw(_("Algumas faturas selecionadas são inválidas ou não estão submetidas."))

# Valida consistência de fornecedor e moeda
suppliers = {inv["supplier"] for inv in invoices}
currencies = {inv["currency"] for inv in invoices}
companies = {inv["company"] for inv in invoices}

if len(suppliers) > 1:
    frappe.throw(_("Todas as faturas selecionadas devem pertencer ao mesmo fornecedor."))
if len(currencies) > 1:
    frappe.throw(_("Todas as faturas selecionadas devem compartilhar a mesma moeda."))
if len(companies) > 1:
    frappe.throw(_("As faturas pertencem a empresas distintas no ERP."))

total_outstanding = sum(inv["outstanding_amount"] for inv in invoices)

if total_outstanding <= 0:
    frappe.throw(_("O valor total pendente das faturas selecionadas deve ser maior que zero."))

try:
    # Criação do Payment Entry consolidado
    payment_entry = frappe.new_doc("Payment Entry")
    payment_entry.payment_type = "Pay"
    payment_entry.party_type = "Supplier"
    payment_entry.party = suppliers.pop()
    payment_entry.company = companies.pop()
    payment_entry.paid_amount = total_outstanding
    payment_entry.received_amount = total_outstanding

    # Adiciona referências para cada fatura individual
    for inv in invoices:
        payment_entry.append("references", {
            "reference_doctype": "Purchase Invoice",
            "reference_name": inv["name"],
            "total_amount": inv["outstanding_amount"],
            "outstanding_amount": inv["outstanding_amount"],
            "allocated_amount": inv["outstanding_amount"]
        })

    payment_entry.insert(ignore_permissions=True)

    # Log de auditoria
    frappe.logger().info(f"Consolidação concluída: {payment_entry.name} criada para {len(invoices)} faturas.")

    return {
        "status": "success",
        "payment_entry": payment_entry.name,
        "total_consolidated": total_outstanding
    }

except Exception as e:
    frappe.db.rollback()
    frappe.log_error(message=frappe.get_traceback(), title="Falha na Consolidação de Faturas")
    frappe.throw(_("Erro ao consolidar faturas: {0}").format(str(e)))

Integrando na Interface do Usuário (ERPNext Client Script)

Para que o usuário final selecione múltiplas faturas diretamente da List View de Purchase Invoice, podemos adicionar uma ação personalizada no menu suspenso:

javascript
frappe.listviewsettings[‘Purchase Invoice’] = {
onload: function(listview) {
listview.page.add
actionitem((‘Consolidar Faturas’), function() {
const selected
docs = listview.getcheckeditems();

        if (selected_docs.length === 0) {
            frappe.msgprint(__('Selecione ao menos uma fatura para consolidar.'));
            return;
        }

        const docnames = selected_docs.map(doc => doc.name);

        frappe.call({
            method: 'seu_app.api.consolidate_purchase_invoices',
            args: { invoice_names: docnames },
            freeze: true,
            freeze_message: __('Processando consolidação...'),
            callback: function(r) {
                if (!r.exc && r.message) {
                    frappe.show_alert({
                        message: __('Entrada de Pagamento {0} criada com sucesso!', [r.message.payment_entry]),
                        indicator: 'green'
                    });
                    frappe.set_route('Form', 'Payment Entry', r.message.payment_entry);
                }
            }
        });
    });
}

};


Boas Práticas e Performance em Lotes Extensos

Como especialista em IA e automação de sistemas corporativos, destaco alguns pontos críticos ao lidar com centenas de registros simultâneos:

  1. Evite get_doc em loops: Carregar o documento completo em memória dentro de loops consome muitos recursos. Utilize frappe.get_all com projeção explícita de campos necessários.
  2. Uso de Filas Assíncronas (Frappe Background Jobs): Se o lote ultrapassar 50 faturas com cálculos complexos de impostos, delegue o processo para frappe.enqueue para não bloquear a interface do usuário.
  3. Camada Preditiva e Detecção de Anomalias: Em projetos avançados, modelos leves de machine learning podem ser acoplados a esse fluxo para verificar divergências atípicas nos valores unitários ou identificar faturas duplicadas antes da consolidação definitiva.

Conclusão e Próximos Passos

Automatizar a seleção e consolidação de faturas de compra no ERPNext elimina tarefas manuais repetitivas, reduz falhas operacionais e acelera o fechamento financeiro da empresa.

Se a sua operação necessita de customizações avançadas no ERPNext, criação de rotinas automatizadas em Python ou integração de fluxos inteligentes aos seus sistemas corporativos, entre em contato para avaliar uma consultoria técnica especializada sob medida para a sua arquitetura.

Preencha o formulário abaixo para que eu consiga entrar em contato com você.