feat(pendientes): cotizador (mensualidades, bloqueo de unidad, totales, numMensualidades), cobranza (recibos, atrasos, mora configurable, recordatorios), soporte (adjuntos, filtros, estados), clientes maestro (catalogos reales), contactos (ficha y duplicados) y permisos de captura por rol
This commit is contained in:
@@ -17,8 +17,10 @@
|
||||
* - Pagos programados: numPagos mensualidades desde MES INICIO
|
||||
* (montoPagado=0; el módulo deriva VENCIDO por fecha).
|
||||
* - Celda de grid con valor > 0 = abono real del mes → AplicacionPago
|
||||
* (fechaPago = mes de la columna) sobre la mensualidad de ese mes;
|
||||
* si no hay mensualidad programada en ese mes → Pago standalone pagado.
|
||||
* (fechaPago = mes de la columna) sobre la mensualidad de ese mes; si no
|
||||
* hay mensualidad programada en ese mes, o el abono excede lo que falta de
|
||||
* la mensualidad, el excedente se registra como Pago standalone ya cubierto
|
||||
* (así ningún abono supera el monto de su pago).
|
||||
*/
|
||||
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
@@ -57,6 +59,7 @@ function norm(h) {
|
||||
}
|
||||
|
||||
const toStr = (v) => (v == null ? null : String(v).trim() || null)
|
||||
const round2 = (x) => Math.round(x * 100) / 100
|
||||
const toFloat = (v) => {
|
||||
if (v == null || v === '') return null
|
||||
if (typeof v === 'number') return v
|
||||
@@ -230,23 +233,27 @@ async function escribir(planesLeidos) {
|
||||
}
|
||||
plan.engancheTotal = pagosProg.reduce((a, p) => a + p.monto, 0)
|
||||
|
||||
// 2) abonos del grid sobre la mensualidad del mismo mes; sobrantes → standalone
|
||||
// 2) abonos del grid sobre la mensualidad del mismo mes. Un abono nunca
|
||||
// puede exceder el monto de su pago: lo que sobra (y los meses sin
|
||||
// mensualidad programada) se registra como pago nuevo ya cubierto, así
|
||||
// Σ montoPagado == dinero recibido y ningún pago queda sobrepagado.
|
||||
const aplicaciones = [] // { idxPago, fechaPago, monto }
|
||||
const standalone = []
|
||||
for (const ab of abonosGrid) {
|
||||
for (const ab of [...abonosGrid].sort((a, b) => a.fecha - b.fecha)) {
|
||||
// idx del arreglo == numPago (el inicial ocupa numPago 0 en el índice 0)
|
||||
const idx = pagosProg.findIndex((p) => p.numPago >= 1 && claveMes(p.fechaVencimiento) === claveMes(ab.fecha))
|
||||
let restante = ab.monto
|
||||
if (idx !== -1) {
|
||||
aplicaciones.push({ idx, fechaPago: ab.fecha, monto: ab.monto })
|
||||
} else {
|
||||
standalone.push({ fecha: ab.fecha, monto: ab.monto })
|
||||
const p = pagosProg[idx]
|
||||
const falta = Math.max(0, round2(p.monto - p.montoPagado))
|
||||
const aplica = Math.min(restante, falta)
|
||||
if (aplica > 0) {
|
||||
aplicaciones.push({ idx, fechaPago: ab.fecha, monto: round2(aplica) })
|
||||
p.montoPagado = round2(p.montoPagado + aplica)
|
||||
restante = round2(restante - aplica)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// aplicar abonos al montoPagado (sin pasar del monto programado)
|
||||
for (const ap of aplicaciones) {
|
||||
const p = pagosProg[ap.idx]
|
||||
p.montoPagado = Math.min(p.monto, p.montoPagado + ap.monto)
|
||||
if (restante > 0) standalone.push({ fecha: ab.fecha, monto: round2(restante) })
|
||||
}
|
||||
|
||||
// Invariante del módulo: Σ montos de TODOS los pagos del plan == engancheTotal
|
||||
|
||||
Reference in New Issue
Block a user