Fix: double discount, dynamic column header, receipt link, remaining installments card

This commit is contained in:
Sidney 2026-05-14 21:19:47 -03:00
parent c2efa1729f
commit 00351031d1
4 changed files with 22 additions and 15 deletions

View File

@ -645,12 +645,12 @@ async function sendEvolutionMessage(asaasPaymentId, eventType, fallbackValorArg
const pdfArrayBuffer = doc.output('arraybuffer'); const pdfArrayBuffer = doc.output('arraybuffer');
const pdfBuffer = Buffer.from(pdfArrayBuffer); const pdfBuffer = Buffer.from(pdfArrayBuffer);
// Upload para o MinIO (Pasta recibos) // Upload para o MinIO (Pasta recibos) — apenas para envio via WhatsApp
const minioFileName = `recibos/recibo_${asaasPaymentId}.pdf`; const minioFileName = `recibos/recibo_${asaasPaymentId}.pdf`;
const minioUrl = await uploadReceiptToStorage(minioFileName, pdfBuffer); const minioUrl = await uploadReceiptToStorage(minioFileName, pdfBuffer);
// Atualiza o link no banco de dados para apontar para o seu MinIO // NÃO sobrescrever transaction_receipt_url — o link do Asaas (público) é salvo pelo webhook
await updateCobranca(asaasPaymentId, { transaction_receipt_url: minioUrl }); // O MinIO é usado apenas para o envio do PDF via WhatsApp
base64Pdf = pdfBuffer.toString('base64'); base64Pdf = pdfBuffer.toString('base64');
fileName = `Recibo-${targetName.replace(/\s+/g, '')}.pdf`; fileName = `Recibo-${targetName.replace(/\s+/g, '')}.pdf`;

View File

@ -281,8 +281,8 @@ app.get('/api/portal/financeiro', authMiddleware, async (req, res) => {
studentId: req.user.studentId, studentId: req.user.studentId,
asaasPaymentId: asaasId, asaasPaymentId: asaasId,
asaasPaymentUrl: jsonP.asaasPaymentUrl || null, asaasPaymentUrl: jsonP.asaasPaymentUrl || null,
amount: Number(db.valor) || jsonP.amount || 0, amount: jsonP.amount || Number(db.valor) || 0,
discount: jsonP.discount || 0, discount: jsonP.amount ? (jsonP.discount || 0) : 0,
dueDate: db.vencimento || jsonP.dueDate, dueDate: db.vencimento || jsonP.dueDate,
status: normalizedStatus, status: normalizedStatus,
paidDate: db.data_pagamento || jsonP.paidDate || null, paidDate: db.data_pagamento || jsonP.paidDate || null,

View File

@ -285,8 +285,10 @@ app.get('/api/portal/financeiro', authMiddleware, async (req, res) => {
studentId: req.user.studentId, studentId: req.user.studentId,
asaasPaymentId: asaasId, asaasPaymentId: asaasId,
asaasPaymentUrl: jsonP.asaasPaymentUrl || null, asaasPaymentUrl: jsonP.asaasPaymentUrl || null,
amount: Number(db.valor) || jsonP.amount || 0, // jsonP.amount = valor BRUTO (ex: 170), db.valor = valor LÍQUIDO do Asaas (ex: 150)
discount: jsonP.discount || 0, // Se o JSON tem o bruto, usa ele + desconto separado. Se não, usa SQL (já líquido) sem desconto.
amount: jsonP.amount || Number(db.valor) || 0,
discount: jsonP.amount ? (jsonP.discount || 0) : 0,
dueDate: db.vencimento || jsonP.dueDate, dueDate: db.vencimento || jsonP.dueDate,
status: normalizedStatus, status: normalizedStatus,
paidDate: db.data_pagamento || jsonP.paidDate || null, paidDate: db.data_pagamento || jsonP.paidDate || null,

View File

@ -96,9 +96,11 @@ export default function Financeiro() {
}; };
const getReceiptLink = (payment: Payment): string | null => { const getReceiptLink = (payment: Payment): string | null => {
if ((payment as any).transactionReceiptUrl) return (payment as any).transactionReceiptUrl; // 1. Verificar campo direto no pagamento (vem do SQL ou JSON)
if ((payment as any).transaction_receipt_url) return (payment as any).transaction_receipt_url; const directUrl = (payment as any).transactionReceiptUrl || (payment as any).transaction_receipt_url;
if (directUrl && typeof directUrl === 'string' && directUrl.startsWith('http')) return directUrl;
// 2. Cross-reference com boletos (tabela SQL separada)
const asaasId = payment.asaasPaymentId || (payment as any).asaas_payment_id; const asaasId = payment.asaasPaymentId || (payment as any).asaas_payment_id;
let boleto = null; let boleto = null;
@ -113,8 +115,10 @@ export default function Financeiro() {
); );
} }
if (!boleto) return null; const boletoUrl = (boleto as any)?.transaction_receipt_url || (boleto as any)?.link_recibo;
return (boleto as any).link_recibo || (boleto as any).transaction_receipt_url || null; if (boletoUrl && typeof boletoUrl === 'string' && boletoUrl.startsWith('http')) return boletoUrl;
return null;
}; };
const handleOpenReceipt = (payment: Payment) => { const handleOpenReceipt = (payment: Payment) => {
@ -122,6 +126,7 @@ export default function Financeiro() {
if (receiptUrl) { if (receiptUrl) {
window.open(receiptUrl, '_blank', 'noopener,noreferrer'); window.open(receiptUrl, '_blank', 'noopener,noreferrer');
} else { } else {
// Fallback: abrir modal local com dados do pagamento
setReceiptPayment(payment); setReceiptPayment(payment);
} }
}; };
@ -244,10 +249,10 @@ export default function Financeiro() {
</div> </div>
<div className="glass-card" style={{ padding: '1.25rem' }}> <div className="glass-card" style={{ padding: '1.25rem' }}>
<p style={{ fontSize: '0.75rem', color: 'var(--color-text-secondary)', fontWeight: 500, marginBottom: '0.5rem' }}> <p style={{ fontSize: '0.75rem', color: 'var(--color-text-secondary)', fontWeight: 500, marginBottom: '0.5rem' }}>
TOTAL DE PARCELAS PARCELAS RESTANTES
</p> </p>
<p style={{ fontSize: '1.375rem', fontWeight: 700 }}> <p style={{ fontSize: '1.375rem', fontWeight: 700, color: payments.filter(p => isPending(p)).length > 0 ? 'var(--color-warning)' : 'var(--color-success)' }}>
{payments.length} {payments.filter(p => isPending(p)).length}
</p> </p>
</div> </div>
</div> </div>
@ -307,7 +312,7 @@ export default function Financeiro() {
<th>Vencimento</th> <th>Vencimento</th>
<th>Valor</th> <th>Valor</th>
<th>Desconto</th> <th>Desconto</th>
<th>A Pagar</th> <th>{filter === 'paid' ? 'Pago' : 'A Pagar'}</th>
<th>Status</th> <th>Status</th>
<th>Ação</th> <th>Ação</th>
</tr> </tr>