Fix: double discount, dynamic column header, receipt link, remaining installments card
This commit is contained in:
parent
c2efa1729f
commit
00351031d1
|
|
@ -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`;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue