129 lines
4.7 KiB
TypeScript
129 lines
4.7 KiB
TypeScript
// SOE — Handler Financeiro
|
|
// Automações financeiras disparadas pelo Event Bus SOE.
|
|
|
|
import { db } from "../../../db/index";
|
|
import { finAccountsReceivable, finAccountsPayable } from "@shared/schema";
|
|
import type { SoeEventPayload } from "../event-bus";
|
|
|
|
export const financialHandler = {
|
|
|
|
// Cria conta a receber automaticamente ao emitir NF-e de venda
|
|
criarContaReceber: async (payload: SoeEventPayload) => {
|
|
const { dados, tenantId } = payload;
|
|
if (!dados.valorTotal || !dados.clienteId) return;
|
|
|
|
try {
|
|
// Verifica se já existe conta a receber para esta NF-e
|
|
if (dados.nfeId) {
|
|
const existente = await db.query.finAccountsReceivable?.findFirst?.({
|
|
where: (t: any, { eq }: any) => eq(t.reference, `nfe:${dados.nfeId}`),
|
|
});
|
|
if (existente) return;
|
|
}
|
|
|
|
const vencimento = dados.vencimento
|
|
|| new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().substring(0, 10);
|
|
|
|
await db.insert(finAccountsReceivable).values({
|
|
tenantId: tenantId ?? 1,
|
|
customerId: dados.clienteId,
|
|
description: `NF-e ${dados.numero || dados.nfeId || "?"} — ${dados.clienteNome || "Cliente"}`,
|
|
amount: String(dados.valorTotal),
|
|
dueDate: vencimento,
|
|
status: "pending",
|
|
category: "vendas",
|
|
paymentMethod: dados.formaPagamento || "outros",
|
|
notes: `Gerado automaticamente pelo SOE ao emitir NF-e`,
|
|
} as any);
|
|
|
|
console.log("[FinancialHandler] Conta a receber criada para NF-e", dados.nfeId || dados.numero);
|
|
} catch (e: any) {
|
|
console.error("[FinancialHandler] criarContaReceber error:", e.message);
|
|
}
|
|
},
|
|
|
|
// Baixa conta a receber após recebimento confirmado
|
|
baixarContaReceber: async (payload: SoeEventPayload) => {
|
|
const { dados } = payload;
|
|
if (!dados.contaReceiverId) return;
|
|
|
|
try {
|
|
await db
|
|
.update(finAccountsReceivable)
|
|
.set({
|
|
status: "received",
|
|
receivedDate: dados.dataRecebimento || new Date().toISOString().substring(0, 10),
|
|
paymentMethod: dados.formaPagamento || "outros",
|
|
} as any)
|
|
.where((finAccountsReceivable as any).id.eq(dados.contaReceiverId));
|
|
} catch (e: any) {
|
|
console.error("[FinancialHandler] baixarContaReceber error:", e.message);
|
|
}
|
|
},
|
|
|
|
// Cria conta a pagar ao receber mercadoria
|
|
criarContaPagar: async (payload: SoeEventPayload) => {
|
|
const { dados, tenantId } = payload;
|
|
if (!dados.valorTotal || !dados.fornecedorId) return;
|
|
|
|
try {
|
|
const vencimento = dados.vencimento
|
|
|| new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().substring(0, 10);
|
|
|
|
await db.insert(finAccountsPayable).values({
|
|
tenantId: tenantId ?? 1,
|
|
supplierId: dados.fornecedorId,
|
|
description: `NF-e Entrada ${dados.numero || "?"} — ${dados.fornecedor || "Fornecedor"}`,
|
|
amount: String(dados.valorTotal),
|
|
dueDate: vencimento,
|
|
status: "pending",
|
|
category: "compras",
|
|
notes: `Gerado automaticamente pelo SOE ao receber compra`,
|
|
} as any);
|
|
} catch (e: any) {
|
|
console.error("[FinancialHandler] criarContaPagar error:", e.message);
|
|
}
|
|
},
|
|
|
|
// Baixa conta a pagar após pagamento
|
|
baixarContaPagar: async (payload: SoeEventPayload) => {
|
|
const { dados } = payload;
|
|
if (!dados.contaPayableId) return;
|
|
|
|
try {
|
|
await db
|
|
.update(finAccountsPayable)
|
|
.set({
|
|
status: "paid",
|
|
paidDate: dados.dataPagamento || new Date().toISOString().substring(0, 10),
|
|
paymentMethod: dados.formaPagamento || "outros",
|
|
} as any)
|
|
.where((finAccountsPayable as any).id.eq(dados.contaPayableId));
|
|
} catch (e: any) {
|
|
console.error("[FinancialHandler] baixarContaPagar error:", e.message);
|
|
}
|
|
},
|
|
|
|
// Reserva estoque ao confirmar venda (notificação — execução no motor)
|
|
reservarEstoque: async (payload: SoeEventPayload) => {
|
|
const { dados } = payload;
|
|
if (!dados.items || dados.motor === "local") return;
|
|
// Log apenas — a reserva real é feita pelo motor Plus/ERPNext
|
|
console.log(
|
|
`[FinancialHandler] Venda ${dados.id} confirmada — ` +
|
|
`${dados.items?.length || 0} item(s) para reserva de estoque`
|
|
);
|
|
},
|
|
|
|
// Alerta de estoque baixo (via console/log — webhook futuro)
|
|
alertaEstoqueBaixo: async (payload: SoeEventPayload) => {
|
|
const { dados } = payload;
|
|
console.warn(
|
|
`[FinancialHandler] ALERTA: Estoque baixo — ` +
|
|
`Produto ${dados.produtoId} / ${dados.produtoNome} ` +
|
|
`(atual: ${dados.estoqueAtual}, mínimo: ${dados.estoqueMinimo})`
|
|
);
|
|
// TODO Fase 5: enviar via WhatsApp/Email usando Motor Comunicação (:8006)
|
|
},
|
|
};
|