I'm a new salesforce developer, and i'm facin my first real world problem in my company.
We have a custom object named "Solicitudes__c", is the detail side of a master relationship of another custom object.
I was asked to calculate the time between stages (Picklist field named "Etapas_de_solicitud__c" with 10 values). My solution is use History object records to calculate the first date to enter one stage, and the last day touching that same stage for every stage. I had to create some sample records and move them through all stages.
Later i came out with this apex class:
public with sharing class SolicitudesTiempos {
public static void tiempos(List<Solicitudes__c> solicitudes) {
//transform the List to Set of Ids
set<id> solicitudesIds = new set<Id>();
for(solicitudes__c s : solicitudes){
solicitudesIds.add(s.id);
}
//list used to update the Solicitudes__c records lateer
List<Solicitudes__c> solicitudesActualizar = new List<Solicitudes__c>();
//gather all Solicitudes__History records.
list<Solicitudes__History> historialBuscado = [SELECT ParentId, NewValue, OldValue, CreatedDate
FROM Solicitudes__History
WHERE ParentId =: solicitudesIds
AND Field = 'Etapas_de_Solicitud__c'
WITH SECURITY_ENFORCED
ORDER BY CreatedDate ASC];
// lists to store the history changes, accordingly.
list<Solicitudes__History> historialCotizacion = new list<Solicitudes__History>();
list<Solicitudes__History> historialAceptacion = new list<Solicitudes__History>();
list<Solicitudes__History> historialPreanalisis = new list<Solicitudes__History>();
list<Solicitudes__History> historialDocumentos = new list<Solicitudes__History>();
list<Solicitudes__History> historialCarga = new list<Solicitudes__History>();
list<Solicitudes__History> historialPendienteAnalizar = new list<Solicitudes__History>();
list<Solicitudes__History> historialComite = new list<Solicitudes__History>();
list<Solicitudes__History> historialCondicionada = new list<Solicitudes__History>();
list<Solicitudes__History> historialAutorizada = new list<Solicitudes__History>();
list<Solicitudes__History> historialPendientedeFirma = new list<Solicitudes__History>();
// loop throughout all solicitudes__history to store the appropiate history record to a list.
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'Cotización' || s.OldValue == 'Cotización'){
historialCotizacion.add(s);
}
}
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'Aceptación' || s.OldValue == 'Aceptación'){
historialAceptacion.add(s);
}
}
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'Preanálisis' || s.OldValue == 'Preanálisis'){
historialPreanalisis.add(s);
}
}
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'Documentos' || s.OldValue == 'Documentos'){
historialDocumentos.add(s);
}
}
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'Aviso a sebas' || s.OldValue == 'Aviso a sebas'){
historialCarga.add(s);
}
}
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'pendiente de analizar' || s.OldValue == 'pendiente de analizar'){
historialPendienteAnalizar.add(s);
}
}
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'comite' || s.OldValue == 'comite'){
historialComite.add(s);
}
}
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'condicionado' || s.OldValue == 'condicionado'){
historialCondicionada.add(s);
}
}
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'autorizada' || s.OldValue == 'autorizada'){
historialAutorizada.add(s);
}
}
for(Solicitudes__History s : historialBuscado){
if(s.NewValue == 'pendiente de firma' || s.OldValue == 'pendiente de firma'){
historialPendientedeFirma.add(s);
}
}
// loop to calcule every Solicitudes__c record time on each stage.
for(Id s : solicitudesIds){
Decimal tiempoCotizacion = SolicitudesTiempos.calcularTiempo(historialCotizacion, s);
Decimal tiempoAceptacion = SolicitudesTiempos.calcularTiempo(historialAceptacion, s);
Decimal tiempoPreanalisis = SolicitudesTiempos.calcularTiempo(historialPreanalisis, s);
Decimal tiempoDocumentos = SolicitudesTiempos.calcularTiempo(historialDocumentos, s);
Decimal tiempoCarga = SolicitudesTiempos.calcularTiempo(historialCarga, s);
Decimal tiempoPendienteAnalizar = SolicitudesTiempos.calcularTiempo(historialPendienteAnalizar, s);
Decimal tiempoComite = SolicitudesTiempos.calcularTiempo(historialComite, s);
Decimal tiempoCondicionado = SolicitudesTiempos.calcularTiempo(historialCondicionada, s);
Decimal tiempoAutorizada = SolicitudesTiempos.calcularTiempo(historialAutorizada, s);
Decimal tiempoPendienteFirma = SolicitudesTiempos.calcularTiempo(historialPendientedeFirma, s);
//store each calculation to the record's field and add it to a collection to update later.
Solicitudes__c solicitud = new Solicitudes__c(Id = s,
Tiempo_cotizacion__c=tiempoCotizacion,
Tiempo_Aceptacion__c=tiempoAceptacion,
Tiempo_preanalisis__c=tiempoPreanalisis,
Tiempo_Documentos__c=tiempoDocumentos,
Tiempo_Carga__c=tiempoCarga,
Tiempo_Por_analizar__c=tiempoPendienteAnalizar,
Tiempo_Comite__c=tiempoComite,
Tiempo_Condicionada__c=tiempoCondicionado,
Tiempo_Autorizada__c=tiempoAutorizada,
Tiempo_Pendiente_Firma__c=tiempoPendienteFirma);
//add the new record to a collection to update later.
solicitudesActualizar.add(solicitud);
}
//update the records
update solicitudesActualizar;
}
//method to calculate time between stages.
private static Decimal calcularTiempo (list<Solicitudes__History> historial, Id solicitudId){
if(historial.size()>0 && historial != null){
dateTime date1;
dateTime date2;
Decimal minutos;
for(Solicitudes__History hist : historial){
if(hist.ParentId == solicitudId && date1 == null){
date1 = hist.CreatedDate;
}else if(hist.ParentId == solicitudId && date1 != null){
date2 = hist.CreatedDate;
}
}
if(date1 != null && date2 != null){
Long firstDate = date1.getTime();
Long finalDate = date2.getTime();
Decimal segundos = (finalDate-firstDate)/1000;
minutos = segundos/60;
return minutos;
}else{
return 0.00;
}
}else{
return 0.00;
}
}
}
Then i came out with an batch apex class:
public class SolicitudesTiemposBatch implements Database.Batchable<SObject> {
public Database.QueryLocator start(Database.BatchableContext bc) {
String query = 'SELECT Id FROM Solicitudes__c '+
'WHERE CreatedDate >= 2022-01-01T00:00:00Z '+
'ORDER BY CreatedDate ASC';
return DataBase.getQueryLocator(query);
}
public void execute(Database.BatchableContext bc, List<Solicitudes__c> scope){
SolicitudesTiempos.tiempos(scope);
}
public void finish(database.BatchableContext bc){
}
}
And create a Test class:
@isTest
public class SolicitudesTiemposBatchTest {
@isTest(SeeAllData=true)
static void test(){
Test.startTest();
SolicitudesTiemposBatch solicitudesBatch = new SolicitudesTiemposBatch();
Database.executeBatch(solicitudesBatch, 200);
Test.stopTest();
System.assertEquals(199,[SELECT count() FROM Solicitudes__C], '199 solicitudes');
}
}
The problem is that when i'm trying to validate in production i see this problem: "System.UnexpectedException: No more than one executeBatch can be called from within a test method. Please make sure the iterable returned from your start method matches the batch size, resulting in one executeBatch invocation. Stack Trace: External entry point"
What i can read searching online, is that you cannot mock History records, OldValue and NewValue are not writable.
What can i do, any improvements/suggestions? Thanks a lot in advanced.