Cuando empecé a trabajar con pagos aplicados en NetSuite, uno de los problemas que más dolores de cabeza me dio fue el famoso límite de 10,000 líneas en las sublistas de transacciones.
El escenario era bastante común: necesitaba crear un Customer Payment o un Vendor Payment y aplicar determinadas transacciones.
En principio, parecía algo sencillo.
El problema aparecía cuando el cliente o proveedor tenía miles de documentos abiertos.
Yo podía obtener perfectamente el internalid de la transacción que necesitaba mediante una búsqueda. Hasta ahí, todo funcionaba correctamente.
El problema empezaba cuando creaba el Payment:
const payment = record.create({
type: record.Type.VENDOR_PAYMENT,
isDynamic: false
})
Y después revisaba la cantidad de líneas disponibles en la sublista apply:
const lineCount = payment.getLineCount({
sublistId: 'apply'
})
Cuando el volumen de documentos era muy grande, NetSuite no me exponía todas las transacciones disponibles.
Y ahí aparecía el famoso límite de 10,000 líneas.
El problema se volvió todavía más extraño
Lo que más me confundía era que yo sí conocía la transacción que necesitaba.
Ya tenía su internalid, lo había obtenido mediante una búsqueda y podía comprobar que el documento existía.
Entonces pensé: si ya conozco el ID, simplemente puedo buscarlo directamente dentro de apply.
Probé con:
const line = payment.findSublistLineWithValue({
sublistId: 'apply',
fieldId: 'doc',
value: String(transactionId)
})
Pero si esa transacción estaba fuera de las primeras líneas que NetSuite había cargado, el resultado era:
-1
Y eso era bastante confuso.
La transacción existía.
El internalid era correcto.
La búsqueda la encontraba.
Pero NetSuite no la tenía disponible dentro de las líneas que había cargado en apply.
De hecho, este mismo problema ha sido comentado por otros desarrolladores de NetSuite: cuando existen más de 10,000 documentos abiertos, getLineCount() puede devolver 10,000 y findSublistLineWithValue() puede devolver -1 para una transacción que está fuera de ese conjunto.
No tenía la solución
Y aquí es donde empezó realmente la investigación.
No tenía una solución clara.
Mi primera reacción fue pensar en diferentes alternativas: filtros, búsquedas, formas de ordenar las líneas, cambiar el modo de creación del record, etc.
Pero ninguna de esas opciones resolvía realmente el problema.
Así que hice lo que normalmente hago cuando me encuentro con una limitación de NetSuite que no conozco: empecé a buscar si alguien más ya había pasado por lo mismo.
Me puse a revisar documentación, foros y conversaciones de otros desarrolladores que trabajan con SuiteScript.
Y después de bastante búsqueda encontré algo que me llamó mucho la atención. esta conversación en NetSuite Professionals
Otros desarrolladores habían tenido exactamente el mismo problema con la sublista apply y el límite de 10,000 registros. En una de esas conversaciones apareció una alternativa que no había considerado: utilizar defaultValues al momento de crear el Payment para especificar las transacciones que quería tener disponibles.
Ahí fue cuando empecé a probar otra estrategia.
La solución que encontré
En lugar de crear el Payment y dejar que NetSuite cargara todas las transacciones disponibles, podía indicarle desde el momento de la creación qué documentos quería manejar.
Por ejemplo, para un Vendor Payment:
const payment = record.create({
type: record.Type.VENDOR_PAYMENT,
isDynamic: false,
defaultValues: {
entity: vendorId,
subsidiary: subsidiaryId,
vendorbills: '100,200,300,400,500'
}
})
La idea cambió completamente.
Ya no estaba diciendo:
“Cárgame todas las transacciones disponibles para este proveedor.”
Ahora estaba intentando decir:
“Quiero trabajar específicamente con estas transacciones.”
Por ejemplo:
Bill 100
Bill 200
Vendor Credit 300
Bill 400
Vendor Credit 500
Y esto fue justamente lo interesante del descubrimiento.
La documentación oficial de Oracle confirma que defaultValues puede utilizarse como parámetro de inicialización en record.create(), aunque los valores de inicialización disponibles dependen del tipo de registro.
En la práctica, la comunidad de desarrolladores de NetSuite ha documentado el uso de vendorbills para limitar los documentos que aparecen en apply en escenarios de Vendor Payment/Vendor Credit, y invoices para escenarios de Customer Payment.
Lo que estaba haciendo mal
Mirando el problema en retrospectiva, creo que mi error inicial fue intentar resolverlo después de crear el Payment.
Mi flujo era:
25,000 transacciones
↓
record.create()
↓
cargar apply
↓
buscar la transacción
↓
límite de 10,000
↓
-1
Pero si yo ya sabía que solamente necesitaba 50 documentos, no tenía mucho sentido intentar trabajar primero con las 25,000 transacciones.
El nuevo enfoque era:
25,000 transacciones
↓
seleccionar las 50 necesarias
↓
record.create()
↓
indicar los documentos
↓
trabajar con esas 50
Y para mí ese fue el verdadero aprendizaje.
El problema no siempre se soluciona intentando superar una limitación. A veces se soluciona cambiando la forma en la que llegamos a ella.
Verificando las líneas de apply
Después de encontrar esta alternativa, obviamente quería comprobar qué estaba sucediendo realmente.
Por eso, después de crear el Payment, revisaba las líneas disponibles:
const lineCount = payment.getLineCount({
sublistId: 'apply'
})
for (let i = 0; i < lineCount; i++) {
log.audit({
title: 'APPLY LINE ' + i,
details: {
doc: payment.getSublistValue({
sublistId: 'apply',
fieldId: 'doc',
line: i
}),
type: payment.getSublistValue({
sublistId: 'apply',
fieldId: 'type',
line: i
})
}
})
}
También podía comprobar directamente si una transacción específica estaba disponible:
const line = payment.findSublistLineWithValue({
sublistId: 'apply',
fieldId: 'doc',
value: String(transactionId)
})
Esto me permitió validar que el enfoque estaba funcionando y, sobre todo, entender mejor qué estaba haciendo NetSuite con la sublista.
¿Por qué decidí documentarlo?
Algo que me gusta de trabajar con tecnologías como NetSuite y SuiteScript es que muchas veces los problemas que encontramos no tienen una respuesta tan evidente.
Puedes revisar la documentación, probar diferentes métodos y aun así terminar buscando durante horas en foros y comunidades.
En mi caso, eso fue exactamente lo que ocurrió.
Yo no conocía esta solución.
Tuve que buscar a otras personas que hubieran tenido un problema similar, revisar conversaciones y probar lo que habían compartido.
Y cuando finalmente encontré una alternativa que resolvía mi caso, pensé que valía la pena documentarla.
Porque probablemente no sea la última persona que se encuentre con un cliente que tenga 15,000, 20,000 o incluso más documentos abiertos.
Y si alguien llega a este artículo después de pasar horas preguntándose por qué findSublistLineWithValue() devuelve -1 cuando la transacción claramente existe, espero que esto le ahorre parte de ese tiempo.
Conclusión
Después de darle muchas vueltas al famoso límite de 10,000 líneas en la sublista apply, terminé entendiendo que el problema no estaba solamente en el límite.
También estaba en cómo estaba abordando la solución.
Al principio intentaba crear el Payment y después buscar dentro de apply la transacción que necesitaba.
Cuando el volumen de documentos era pequeño, funcionaba perfectamente.
Pero cuando el cliente o proveedor tenía miles de documentos, la estrategia dejaba de ser confiable porque la transacción podía estar fuera de las líneas que NetSuite había cargado.
La solución que encontré fue utilizar defaultValues para indicarle a NetSuite qué documentos quería manejar desde el momento de crear el Payment.
Por ejemplo:
defaultValues: {
vendorbills: transactionIds.join(',')
}
o, para Customer Payment:
defaultValues: {
invoices: transactionIds.join(',')
}
Así podía pasar de algo como esto:
25,000 transacciones
↓
apply
↓
límite 10,000
↓
buscar
↓
-1
a algo mucho más controlado:
25,000 transacciones
↓
seleccionar las necesarias
↓
record.create()
↓
apply
↓
trabajar con ellas
Para mí, la lección terminó siendo bastante sencilla:
En lugar de pelear contra el límite, intenta evitar llegar a él.
Y sobre todo, si te encuentras con una limitación de NetSuite que parece no tener sentido, busca primero si alguien más ya tuvo el mismo problema.
Muchas veces la solución está escondida en una conversación de un foro, en una respuesta de otro desarrollador o en un detalle de la documentación que no habíamos considerado.
En mi caso, encontrar esa solución me llevó tiempo.
Por eso decidí documentarla.