import("AttributeFilter_lib");
import("ClassificationComplexIndicatorRegistry_basic");
import("ClassificationIndicatorFieldRegistry_basic");
import("Classification_lib");
import("Context_lib");
import("JditoFilter_lib");
import("Organisation_lib");
import("Sql_lib");
import("Util_lib");
import("system.db");
import("system.eMath");
import("system.entities");
import("system.logging");
import("system.translate");
import("system.util");

/**
 * Methods to manage classifications.<br>
 * <b>Do not create an instance of this!</b>
 *
 * @class
 */
function ClassificationUpdateUtils() {}

/**
 * Refresh function that get's used by the updateClassifications_serverProcess and the openClassificationOverView Actions at Organisation and Salesproject.<br>
 * There are three ways to run this: only recalculating the outdated data (and updating if needed), only for a specific object and returning it's classification (and updating it's stored classification) or recalculating ALL the classifications (and updating if needed).<br>
 * ClassificationTypes are being flagged as Outdated accordingly, everytime someone configures something in ClassificationAdmin.<br>
 * The classificationStorageDatasets are being flagged as outdated as soon as you update anything regarding the corresponding object or insert/update 
 * anything with a dependency set to said object.<br>
 * 
 * Calling this function without any params leads to it updating only for the datasets that have been flagged as outdated.
 * <p>
 * First it checks whether outdated classificationStorages and outdated classificationTypes exist
 * and always updates the classificationStorages first before updating the for the outdated classificationTypes.
 * 
 * -In the classificationStorage runtrough it calculates the complete classification for every outdated classificationStorage, entirely 
 * from scratch and updates the classificationStorage
 * -In the classificationType runtrough it also updates the classificationStorage dataset, but this time 
 *  -for every object of said objectType
 *  -only recalculates the flagged ones and relies on the already stored values of the not flagged classificationTypes if they are from another classificationGroup
 *  <p>
 *  When recalculateAll_param is set to true: now the outdated flag is being ignored and all classificationtypes as outdated (only one runtrough needed)
 *  <p>
 *  When pSingleRefreshRowId we only update said object and return the achieved classifications (the stored classificationStorage string is also being updated if needed)
 * 
 * @param {Boolean}[pRefreshAll]                            <p>
 *                                                          Whether or not to recalculate and refresh all classifications<br>
 *                                                          
 * @param {String}[pSingleRefreshRowId]                    <p>
 *                                                          object row id of the dataset you want the classification for (also returns the 
 *                                                          classification as formatted Object for Classification_entity jDito contentprocess)<br>
 *                                                          
 * @param {String}[pObjectType]                            <p>
 *                                                          The Object type of the object row id. (Mandatory if pSingleRefreshRowId is set!)<br>
 *                                                          This is normally just the Context name<br>
 *                                                          
 * @return {Object}                                          <p>
 *                                                           If pSingleRefreshRowId is set: Formatted Object to return to Classification_entity jDito 
 *                                                           contentprocess to show the achieved classifications (the stored classificationStorage string 
 *                                                           is also being updated if needed)<br>
 *                                                           Else: Output in log how many Datasets have and have not been updated.
 */
ClassificationUtils.executeUpdating = function(pRefreshAll, pSingleRefreshRowId, pObjectType) 
{
    var outputInfo = "updateClassifications_serverProcess output is:\n";//information how much data has been modified
    var singleRefreshRowId = false;
    var recalculateAll = false;
    var objectType_param;

    if (pSingleRefreshRowId)
    {
        singleRefreshRowId = pSingleRefreshRowId;
        objectType_param = pObjectType;
    }
    else if (pRefreshAll)
    {
        recalculateAll = pRefreshAll;
    }

    var buildClassificationObjects = ClassificationUpdateHelper._buildClassificationFilterObjects(recalculateAll, singleRefreshRowId);
    var stopper = false;
    var buildOutdatedStoredClassificationObjects = ClassificationUpdateHelper._buildOutdatedStoredClassificationObject(recalculateAll, singleRefreshRowId, pObjectType);

    if (buildOutdatedStoredClassificationObjects && buildOutdatedStoredClassificationObjects["objectTypes"]
            && buildOutdatedStoredClassificationObjects["objectTypes"].length > 0)
    {
        while(buildOutdatedStoredClassificationObjects["objectTypes"].length > 0 && !stopper)
        {
            if (singleRefreshRowId)
            {
                stopper = true;
            }
            var returnAfterUpdate = ClassificationUpdateHelper._updateOutdatedDatasets(null, buildOutdatedStoredClassificationObjects, singleRefreshRowId, outputInfo);//run updating for this set of Datasets
            if(returnAfterUpdate && returnAfterUpdate["outputInfo"])
            {
                outputInfo = returnAfterUpdate["outputInfo"];
            }
            if (buildOutdatedStoredClassificationObjects["objectTypes"].length > 0 && !stopper)
            {
                buildOutdatedStoredClassificationObjects = ClassificationUpdateHelper._buildOutdatedStoredClassificationObject(recalculateAll, singleRefreshRowId, objectType_param);
            }
        }
        
    }
    if (buildClassificationObjects["objectTypesThatNeedUpdate"] && buildClassificationObjects["objectTypesThatNeedUpdate"].length > 0 && !singleRefreshRowId)
    {
        var update = ClassificationUpdateHelper._updateOutdatedDatasets(buildClassificationObjects, null, null, outputInfo);//run updating for this set of Datasets
        outputInfo = update["outputInfo"];
    }
    else if(!returnAfterUpdate || !returnAfterUpdate["outputInfo"])
    {
        outputInfo = outputInfo  + "No Data has been updated, everything already up-to-date";
    }

    if (returnAfterUpdate && !returnAfterUpdate["outputInfo"])
    {
        return ClassificationUpdateHelper._formatOutputForDisplayingValues(returnAfterUpdate, singleRefreshRowId);
    }
    logging.log(outputInfo); //return outputInformation in log instead of returning

    return null;
};

/**
* Throws an error if one of the values is configured the wrong way (more than one children, wrong condition or even wrong filterfield)<p><br>
* 
* @param {Object}pClassificationType                      <p>
*                                                         classificationType as built as from pIndicatorObj<br>
*                                                          
* @param {Boolean}[pIsAttribute]                          <p>
*                                                         Whether or not it's an attribute<br>
*                                                          
*/
ClassificationUtils.validateUserSetValues = function(pClassificationType, pIsAttribute)
{
    var errorMessage = "";
    if(pClassificationType["fieldType"] == $ClassificationFieldTypes.DROPDOWN())
    {
        for (var filterValue in pClassificationType["values"])
        {
            var parsedFilter = JSON.parse(filterValue);
            if(parsedFilter["filter"]["childs"].length != 1)
            {
                errorMessage += translate.withArguments("wrong configuration for '%0'", [pClassificationType["field"]]) + " " + translate.text("only use simple filters") +"\n";
            }
            else if((!pIsAttribute && parsedFilter["filter"]["childs"][0]["name"] != pClassificationType["field"]) || (pIsAttribute && !parsedFilter["filter"]["childs"][0]["name"].includes(pClassificationType["field"])))
            {
                errorMessage += translate.withArguments("wrong configuration for '%0'", [pClassificationType["field"]]) + " " + translate.text("only filter using the specified indicatorfield") +"\n";
            }
            else if(parsedFilter["filter"]["childs"][0]["operator"] != "EQUAL")
            {
                errorMessage += translate.withArguments("wrong configuration for '%0'", [pClassificationType["field"]]) + " " + translate.text("please only filter using 'equal'") +"\n";
            }
        }
    }
    if(errorMessage != "")
    {
        throw errorMessage;
    }
}


function ClassificationUpdateHelper() {}

/**
* Searches for pClassificationTypeId in pIndicatorObj and returns it's object<p><br>
* 
* @param {Object}pClassificationTypeId                    <p>
*                                                         condition of which classificationtypes to update<br>
*                                                          
* @param {Object}pIndicatorObj                            <p>
*                                                         return of buildindicatorObj()[objectType].<br>
*                                                          
*                                                          
* @return {Object}                                         <p> (empty Strings and Objects if not found)
*                                                          {"classificationGroupId": classificationGroupId
*                                                          , "fieldType": fieldType
*                                                          , "field": field
*                                                          , "maxPercent": maxPercent
*                                                          , "points": points
*                                                          , "values": {value: pointsForValue}}<br>
*/
ClassificationUpdateHelper._searchIndicatorObj = function(pClassificationTypeId, pIndicatorObj)
{
    for(var typeKind in pIndicatorObj)
    {
        for(var classificationTypeId in pIndicatorObj[typeKind])
        {
            if (pClassificationTypeId == classificationTypeId)
            {
                if (typeKind != $ClassificationIndicatorTypes.ATTRIBUTE())
                {
                    return pIndicatorObj[typeKind][classificationTypeId];
                }
                else
                {
                    pIndicatorObj[typeKind][classificationTypeId]["field"] = newSelect("ATTRIBUTE_NAME")
                                                        .from("AB_ATTRIBUTE")
                                                        .where("AB_ATTRIBUTE.AB_ATTRIBUTEID", AttributeSearchNameCoder.decode(pIndicatorObj[typeKind][classificationTypeId]["field"])["id"]).cell();
                    return pIndicatorObj[typeKind][classificationTypeId];
                }
            }
        }
    }
    return {"classificationGroupId": ""
            , "fieldType": ""
            , "field": ""
            , "maxPercent": ""
            , "points": ""
            , "values": {}};
};

/**
* Formats the output in an TwoDimensional array for Classification_entity and returns it (stringified!)<p><br>
* 
* @param {Object}pReturn                                  <p>
*                                                         return of ClassificationUpdateHelper._updateOutdatedDatasets()<br>
*                                                          
* @param {Object}pUid                                     <p>
*                                                         objectRowId we want to show the classifications for<br>
*                                                          
*                                                          
* @return {String}                                        <p> stringified contentProcessArray<br>
*/
ClassificationUpdateHelper._formatOutputForDisplayingValues = function(pReturn, pUid)
{
    var indicatorObj = pReturn["indicatorObj"];
    var achievedScoresObject = pReturn["achievedScoresObject"][pUid];
    var orderedGroups = pReturn["orderedGroups"];
    var classificationString = pReturn["newGradingString"];
    var groupNameObj = {};
    var contentProcessArray = [];
    var groupNames = newSelect("CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID, CLASSIFICATIONGROUP.TITLE")
                        .from("CLASSIFICATIONGROUP")
                        .table();
    for (let i = 0; i < groupNames.length; i++)
    {
        groupNameObj[groupNames[i][0]] = "";
        groupNameObj[groupNames[i][0]] = groupNames[i][1];
    }
    var groupResultObj = {};
    for (var group in orderedGroups)
    {
        var groupId = orderedGroups[group]["0"];
        groupResultObj[groupId] = {};
        groupResultObj[groupId]["points"] = 0;
        groupResultObj[groupId]["maxPoints"] = 0;
        contentProcessArray.push([util.getNewUUID(), groupId, translate.text(groupNameObj[groupId]), "", "", "", "", ""]);
        for (var typeId in achievedScoresObject[groupId])
        {
            var typeInfo = ClassificationUpdateHelper._searchIndicatorObj(typeId, indicatorObj);

            if (achievedScoresObject[groupId][typeId]["key"] || achievedScoresObject[groupId][typeId]["key"] == "0")
            {
                contentProcessArray.push([util.getNewUUID(), groupId, "", typeId, achievedScoresObject[groupId][typeId]["fieldDisplay"], achievedScoresObject[groupId][typeId]["key"]
                        , achievedScoresObject[groupId][typeId]["value"], achievedScoresObject[groupId][typeId]["points"] + "/" + achievedScoresObject[groupId][typeId]["maxPoints"] + " " + translate.text("Points")]);
                groupResultObj[groupId]["points"] = eMath.addInt(parseInt(groupResultObj[groupId]["points"]), parseInt(achievedScoresObject[groupId][typeId]["points"]));
                groupResultObj[groupId]["maxPoints"] = eMath.addInt(parseInt(groupResultObj[groupId]["maxPoints"]), parseInt(achievedScoresObject[groupId][typeId]["maxPoints"]));
            }
            else
            {
                contentProcessArray.push([util.getNewUUID(), groupId, "", typeId, achievedScoresObject[groupId][typeId]["fieldDisplay"], "-"
                        , "-", "0" + "/" + typeInfo["points"] * typeInfo["maxPercent"] / 100 + " " + translate.text("Points")]);
                groupResultObj[groupId]["points"] = eMath.addInt(parseInt(groupResultObj[groupId]["points"]), parseInt(0));
                groupResultObj[groupId]["maxPoints"] = eMath.addInt(parseInt(groupResultObj[groupId]["maxPoints"]), parseInt(typeInfo["points"] * typeInfo["maxPercent"] / 100));
            }
        }
    }
    var position = 0;
    for (let i = 0; i < contentProcessArray.length; i++)
    {
        if (contentProcessArray[i][2] && contentProcessArray[i][2] != "")
        {
            contentProcessArray[i][2] = ClassificationUtils.formatDisplaySummaryForGroup(groupResultObj[contentProcessArray[i][1]]["points"], groupResultObj[contentProcessArray[i][1]]["maxPoints"], contentProcessArray[i][2], classificationString.substring(position, position + 1))
            position++;
        } 
    }

    return JSON.stringify(contentProcessArray);
};

/**
* Helper function, this get's used in ClassificationUpdateHelper._handleEntityFields, ClassificationUpdateHelper._handleAttributes an ClassificationUpdateHelper._handleComplex to get the correct value of the numbervalue<p><br>
* 
* @param {Array}pAttributeValues                          <p>
*                                                         All values this object has for this attribute<br>
*                                                          
* @param {Object}pClassificationType                      <p>
*                                                         as returned by buildindicatorObj()<br>
*                                                          
* @param {Object}pColumns                                 <p>
*                                                         columns of the objectType<br>
*                                                          
* @param {Object}pRows                                    <p>
*                                                         rows of the dataset<br>
*                                                         
* @param {String}pValue                                   <p>
*                                                         the valeu depending on the indicatortype<br>
*                                                         
* @param {boolean}pIsDate                                 <p>
*                                                         true if date, since we handle those a little different<br>
*                                                          
* @return {Object}                                        <p>{"key": 0
*                                                           , "percent": 0
*                                                           , "value": "-"} <- if none, else the actual values;
*/
ClassificationUpdateHelper._getCorrectNumberValue = function(pAttributeValues, pClassificationType, pColumns, pRows, pValue, pIsDate)
{
    var storedValues;
    if (pAttributeValues == undefined)//this record doesn't have a value for that attribute
    {
        storedValues = "ignoreThis";
    }
    else if (pAttributeValues)//atleast one value exists for this attribute
    {
        storedValues = pAttributeValues;
    }
    else if (pColumns)//fieldValue -> get from pRows
    {
        storedValues = [pRows[pColumns.indexOf(pClassificationType["field"], 0)]];
    }
    else
    {
        storedValues = [pValue];
    }
    var achievedPercent = {};
    achievedPercent = {"key": 0
                        , "percent": 0
                        , "value": "-"};

    if (pIsDate)
    {
        var orderedObject = {};
        Object.keys(pClassificationType["values"])
                    .sort()
                    .forEach(function(v, i) {
                        orderedObject[v] = pClassificationType["values"][v];
                     });
        pClassificationType["values"] = orderedObject;
    }

    for(var value in pClassificationType["values"])
    {
        if (storedValues != "ignoreThis")
        {
            for (var val in storedValues)
            {
                if (parseInt(storedValues[val]) >= parseInt(value) && parseInt(storedValues[val]) > parseInt(achievedPercent["key"]))
                {

                    achievedPercent = {};
                    achievedPercent["key"] = value;
                    achievedPercent["value"] = storedValues[val];
                    achievedPercent["percent"] = pClassificationType["values"][value];
                }
            }
        }
    }

    return{"key": achievedPercent["key"]
            , "value": achievedPercent["value"]
            , "maxPoints": pClassificationType["maxPercent"]/100*pClassificationType["points"]
            , "points": achievedPercent["percent"]/100*pClassificationType["points"]
            };
};

/**
* Builds the updateStatments.<p><br>
* 
* @param {Object}pAchievedScores                          <p>
*                                                         AchievedScoresObject built by handlEntityFields(), handleAttributes() and handleComplex()<br>
*                                                          
* @param {Object}pGradingObject                           <p>
*                                                         gradingObject as returned by ClassificationUpdateHelper._buildGradingObject()<br>
*                                                          
* @param {Object}pOrderedGroups                           <p>
*                                                         classificationGroups in the correct order (so we can update at the correct positions)<br>
*                                                          
* @param {Object}pCurrentValue                            <p>
*                                                         current classificationStorage string<br>
*                                                         
* @param {String}pRowId                                   <p>
*                                                         row Id of the current dataset<br>
*                                                         
* @param {boolean}pRowType                                <p>
*                                                         true if date, since we handle those a little different<br>
*                                                          
* @param {boolean}pUpdateStatements                       <p>
*                                                         the updateStatements we already have, we simply add ours to those<br>
*                                                          
* @param {String}pOutputInformation                       <p>
*                                                         we pass the outputinformation trough so we can add to the string<br>
*                                                          
* @return {Object}                                        <p>{"updateStatements": updateStatements
*                                                               , "outputInformation": outputInformation
*                                                               , "newGradingString": newGradingString};
*/
ClassificationUpdateHelper._buildUpdateClassificationValueStatements = function(pAchievedScores, pGradingObject, pOrderedGroups, pCurrentValue, pRowId, pRowType, pUpdateStatements, pOutputInformation)
{
    var pointsPerGroupObj = {};
    var newGradingString = pCurrentValue;
    for(var position in pOrderedGroups)
    {
        var groupId = pOrderedGroups[position][0];
        for(var type in pAchievedScores[groupId])
        {
            if (pAchievedScores[groupId][type]["points"] || pAchievedScores[groupId][type]["points"] == 0)
            {
                if (!pointsPerGroupObj.hasOwnProperty(groupId))
                {
                    pointsPerGroupObj[groupId] = {};
                    pointsPerGroupObj[groupId]["points"] = 0;
                    pointsPerGroupObj[groupId]["maxPoints"] = 0;
                }
                pointsPerGroupObj[groupId]["points"] = eMath.addInt(parseInt(pointsPerGroupObj[groupId]["points"]), parseInt(pAchievedScores[groupId][type]["points"]));
                pointsPerGroupObj[groupId]["maxPoints"] = eMath.addInt(parseInt(pointsPerGroupObj[groupId]["maxPoints"]), parseInt(pAchievedScores[groupId][type]["maxPoints"]));
            }
        }
        if (pointsPerGroupObj && pointsPerGroupObj[groupId])
        {
            newGradingString = StringUtils.replaceAt(newGradingString, position, ClassificationUtils.getGradingFromObject(pGradingObject, groupId, pointsPerGroupObj[groupId]["points"], pointsPerGroupObj[groupId]["maxPoints"]));
        }
    }

    if (pCurrentValue != newGradingString)
    {
        pUpdateStatements.push(["CLASSIFICATIONSTORAGE", ["CLASSIFICATIONVALUE", "OUTDATED"], null, [newGradingString, "0"], newWhere("CLASSIFICATIONSTORAGE.OBJECT_ROWID", pRowId).build()]);
        pOutputInformation[pRowType].updatedElements += 1;
    }
    else
    {
        pUpdateStatements.push(["CLASSIFICATIONSTORAGE", ["OUTDATED"], null, ["0"], newWhere("CLASSIFICATIONSTORAGE.OBJECT_ROWID", pRowId).build()]);
        pOutputInformation[pRowType].nonChangedElements++;
    }

    return {"updateStatements": pUpdateStatements
            , "outputInformation": pOutputInformation
            , "newGradingString": newGradingString};
};

/**
* Builds the updateStatments.<p><br>
* 
* @param {Array}pRowIds                                   <p>
*                                                         rowIds of datasets<br>
*                                                          
* @param {String}pCurrentObjectType                       <p>
*                                                         current Object Type we want the attributeObject for<br>
*                                                          
* @return {Object}                                        <p>attributeObj[objectRowId][encodedName] = [values];;
*/
ClassificationUpdateHelper._buildAttributeObject = function(pRowIds, pCurrentObjectType)
{
    var idColumn = pCurrentObjectType.toUpperCase() + "ID";
    if (pCurrentObjectType == "Organisation")
    {
        idColumn = "CONTACTID";
    }
    var attributeObj = {};
    if (pRowIds && pRowIds.length > 0)
    {
        var objectRowIds = [];
        for (let i = 0; i < pRowIds.length; i++)
        {
            objectRowIds.push(pRowIds[i][idColumn]);
        }

        //select attributevalues for the objectRowIds
        var attributeArray = newSelect(["AB_ATTRIBUTERELATION.OBJECT_ROWID", "AB_ATTRIBUTERELATION.AB_ATTRIBUTE_ID", "AB_ATTRIBUTERELATION.AB_ATTRIBUTERELATIONID",
                                        SqlBuilder.caseWhen(newWhere("AB_ATTRIBUTERELATION.CHAR_VALUE is not null"))
                                                            .then("AB_ATTRIBUTERELATION.CHAR_VALUE")
                                                            .elseValue(SqlBuilder.caseWhen(newWhere("AB_ATTRIBUTERELATION.ID_VALUE is not null"))
                                                                                    .then("AB_ATTRIBUTERELATION.ID_VALUE")
                                                                                    .elseValue("null")
                                                                                    .toString())
                                                            .toString()
                                            ,SqlBuilder.caseWhen(newWhere("AB_ATTRIBUTERELATION.INT_VALUE is not null"))
                                                            .then("AB_ATTRIBUTERELATION.INT_VALUE")
                                                            .elseValue(SqlBuilder.caseWhen(newWhere("AB_ATTRIBUTERELATION.NUMBER_VALUE is not null"))
                                                                                    .then("AB_ATTRIBUTERELATION.NUMBER_VALUE")
                                                                                    .elseValue("null")
                                                                                    .toString())
                                                            .toString()
                                        , "AB_ATTRIBUTE.ATTRIBUTE_TYPE"])
                        .from("AB_ATTRIBUTERELATION")
                        .join("AB_ATTRIBUTE", "AB_ATTRIBUTE_ID = AB_ATTRIBUTEID")
                        .where("AB_ATTRIBUTERELATION.OBJECT_ROWID", objectRowIds, SqlBuilder.IN())
                        .table();



        var helperObject = {};
        let objectRowId, attributeId, attributeRelationId, value, numericValue, attributeType;
        //assign them to attributeObj for easier access 
        for (let element  in attributeArray)
        {
            [objectRowId, attributeId, attributeRelationId, value, numericValue, attributeType] = attributeArray[element];
            var encodedName = AttributeSearchNameCoder.encode(attributeId, attributeType);

            if (!helperObject.hasOwnProperty(objectRowId))
            {
                attributeObj[objectRowId] = {};
                helperObject[objectRowId] = "";

            }
            if (!helperObject.hasOwnProperty(objectRowId + encodedName))
            {
                attributeObj[objectRowId][encodedName] = []; //same logic as before
                helperObject[objectRowId + encodedName] = "";
            }
            if (!helperObject.hasOwnProperty(attributeRelationId)) //objectRowId
            {
                let valueToPush = value && value != "" ? value : numericValue;
                attributeObj[objectRowId][encodedName].push(valueToPush);

                helperObject[attributeRelationId] = "";
            }
        }
    }
    return attributeObj;
};

//helper Function for better code-readability
ClassificationUpdateHelper._processObjectValuesBatchFn = function(pBatchData, pIndicatorObj, pAchievedScoresObject, pGradingObject, pOrderedGroups, pCurrentObjectType, pOutputInformation, pFieldDisplayObject, pFilteredRecsObj, pClassificationTypesNoFilterObj)
{
    var achievedScoresObject = pAchievedScoresObject;
    var attributeObj = ClassificationUpdateHelper._buildAttributeObject(pBatchData, pCurrentObjectType);//call for every "page"
    var updateStatements = [];

    var idColumn = pCurrentObjectType.toUpperCase() + "ID"
    if (pCurrentObjectType == "Organisation")
    {
        idColumn = "CONTACTID";
    }
    //logic from above applies here aswell
    for (let ii = 0; ii < pBatchData.length; ii++) 
    {
        var currentDataset = pBatchData[ii];
        if (currentDataset["CLASSIFICATIONVALUE"] && currentDataset["CLASSIFICATIONVALUE"] != "")
        {
            var columns = [];
            var values = [];

            for (var property in currentDataset)
            {
               if (!currentDataset.hasOwnProperty(property))
               {
                  continue;
               }
               columns.push(property);
               values.push(currentDataset[property]);
            }

                achievedScoresObject = ClassificationUpdateHelper._handleEntityFields(pIndicatorObj[$ClassificationIndicatorTypes.ENTITYFIELD()], values, columns, currentDataset[idColumn], achievedScoresObject, pFieldDisplayObject[pCurrentObjectType], pFilteredRecsObj, pClassificationTypesNoFilterObj, pCurrentObjectType);
                achievedScoresObject = ClassificationUpdateHelper._handleAttributes(pIndicatorObj[$ClassificationIndicatorTypes.ATTRIBUTE()], attributeObj, currentDataset[idColumn], achievedScoresObject, pFieldDisplayObject[pCurrentObjectType], pFilteredRecsObj, pClassificationTypesNoFilterObj, pCurrentObjectType);
                achievedScoresObject = ClassificationUpdateHelper._handleComplex(pIndicatorObj[$ClassificationIndicatorTypes.COMPLEX()], pCurrentObjectType, currentDataset[idColumn], achievedScoresObject, pFieldDisplayObject[pCurrentObjectType], pFilteredRecsObj, pClassificationTypesNoFilterObj);

                var buildUpdateClassificationValueStatements = ClassificationUpdateHelper._buildUpdateClassificationValueStatements(achievedScoresObject[currentDataset[idColumn]], pGradingObject, pOrderedGroups, currentDataset["CLASSIFICATIONVALUE"], currentDataset[idColumn], pCurrentObjectType, updateStatements, pOutputInformation);
                updateStatements = buildUpdateClassificationValueStatements["updateStatements"];
                pOutputInformation = buildUpdateClassificationValueStatements["outputInformation"];

        }
    }
    db.execute(updateStatements);
    var gradingString = buildUpdateClassificationValueStatements != undefined ? buildUpdateClassificationValueStatements["newGradingString"] : "";
    return {"achievedScoresObject": achievedScoresObject
            , "outputInformation": pOutputInformation
            , "newGradingString": gradingString};
};


/**
* Get's the entityfields of pCurrentObjectType by using ClassificationUtils.getEntityFields(), adds the uid column to it and returns it as array<p><br>
* 
* @param {Array}pCurrentObjectType                        <p>
*                                                         Object type you want the fields for<br>
*                                                          
* @return {Array}                                         <p>fields;
*/
ClassificationUpdateHelper._getEntityFields = function(pCurrentObjectType)
{
    var fieldArray = [];
    var entityFields = ClassificationUtils.getEntityFields(pCurrentObjectType);
    for(var field in entityFields)
    {
        fieldArray.push(entityFields[field][0]);
    }
    fieldArray.push("CLASSIFICATIONVALUE");
    if (pCurrentObjectType == "Organisation")
    {
        fieldArray.push("CONTACTID");
    }
    else
    {
        fieldArray.push(pCurrentObjectType.toUpperCase() + "ID");
    }


    return fieldArray;
};

/**
 * handles the complex indicators so you can add them to achievedScoresObject  <p><br>
 * 
 * @param {Object}pClassificationTypes                     <p>
 *                                                         as returned by buildindicatorObj()<br>
 *                                                          
 * @param {String}pObject                                  <p>
 *                                                         current Object Type<br>
 *                                                          
 * @param {String}pUid                                     <p>
 *                                                         current Object uid<br>
 *                                                          
 * @param {Object}pAchievedScoresObject                    <p>
 *                                                         achievedScoresObj we add ours to this<br>
 *                                                          
 * @param {Array[[][]]}pFieldDisplayObject                 <p>
 *                                                         twodimensional array with value and key so we can get the displayValues<br>  
 *                                                         
 * @param {Object}pFilteredRecsObj                         <p>
 *                                                         as returned by ClassificationUpdateHelper._buildFilteredRecsObject()<br>  
 *                                                         
 * @param {Object}pClassificationTypesNoFilterObj          <p>
 *                                                         as built by ClassificationUpdateHelper._buildFilterAndNoFilterObj()<br>  
 *                                                          
 * @return {Object}                                        <p>pAchievedScoresObject;
 */
 ClassificationUpdateHelper._handleComplex = function(pClassificationTypes, pObject, pUid, pAchievedScoresObject, pFieldDisplayObject, pFilteredRecsObj, pClassificationTypesNoFilterObj)
 {
     for(var type in pClassificationTypes)
     {
         if(ClassificationUpdateHelper._isRelevant(type, pUid, pClassificationTypes[type]["filter"], pFilteredRecsObj, pClassificationTypesNoFilterObj, pObject))
         {
             var achievedValueArray = $ClassificationComplexIndicatorRegistry[pClassificationTypes[type]["field"]]().getValueFn(pObject, pUid);

             for (var valueScore in pClassificationTypes[type]["values"])
             {
                 if (!pAchievedScoresObject.hasOwnProperty(pUid))
                 {
                     pAchievedScoresObject[pUid] = {};
                 }
                 if (!pAchievedScoresObject[pUid].hasOwnProperty(pClassificationTypes[type]["classificationGroupId"]))
                 {
                     pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]] = {};
                 }

                 if (!pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]].hasOwnProperty(type))
                 {   
                     pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type] = {};
                     pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["key"] = "-";
                     pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["value"] = "-";
                     pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["maxPoints"] = pClassificationTypes[type]["maxPercent"] / 100 * pClassificationTypes[type]["points"];
                     pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["points"] = 0;
                     pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["fieldDisplay"] = ClassificationUtils.getFieldDisplayValue(pFieldDisplayObject, pClassificationTypes[type]["field"]);
                 }

                 for (var value in achievedValueArray)
                 {
                     if (pClassificationTypes[type]["fieldType"] == $ClassificationFieldTypes.NUMBER() || pClassificationTypes[type]["fieldType"] == $ClassificationFieldTypes.INTEGER()
                         || pClassificationTypes[type]["fieldType"] == $ClassificationFieldTypes.DATE())
                     {
                         var numberValue;
                         if (pClassificationTypes[type]["fieldType"] == $ClassificationFieldTypes.DATE())
                         {
                             numberValue = ClassificationUpdateHelper._getCorrectNumberValue(false, pClassificationTypes[type], false, null, parseInt(achievedValueArray), true);
                         }
                         else
                         {
                             numberValue = ClassificationUpdateHelper._getCorrectNumberValue(false, pClassificationTypes[type], false, null, parseInt(achievedValueArray));
                         }

                         pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["key"] = numberValue["key"];
                         if (pClassificationTypes[type]["fieldType"] == $ClassificationFieldTypes.DATE())
                         {
                             pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["value"] = $ClassificationComplexIndicatorRegistry[pClassificationTypes[type]["field"]]().getDisplayValueFn(numberValue["key"].toString());
                         }
                         else
                         {
                             pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["value"] = numberValue["value"];
                         }
                         pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["maxPoints"] = numberValue["maxPoints"];
                         pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["points"] = numberValue["points"];
                     }
                     else
                     {
                         if (achievedValueArray[value] == valueScore)
                         {
                             pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["maxPoints"] = pClassificationTypes[type]["maxPercent"] / 100 * pClassificationTypes[type]["points"];
                             //replace if the value is greater than the one we already have
                             if (parseInt(pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["points"]) < parseInt(pClassificationTypes[type]["values"][valueScore])*parseInt(pClassificationTypes[type]["points"]) / 100)
                             {                                
                                 pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["points"] = parseInt(pClassificationTypes[type]["values"][valueScore]) * parseInt(pClassificationTypes[type]["points"]) / 100;
                                 pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["key"] = valueScore;
                                 pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["value"] = $ClassificationComplexIndicatorRegistry[pClassificationTypes[type]["field"]]().getDisplayValueFn(valueScore);

                             }
                         }
                     }
                 }
             }
         }
     }
     return pAchievedScoresObject;
 };
 
/**
* handles the attribute indicators so you can add them to achievedScoresObject  <p><br>
* 
* @param {Object}pClassificationTypes                     <p>
*                                                         as returned by buildindicatorObj()<br>
*                                                         
* @param {Object}attributeObj                             <p>
*                                                         as returned by ClassificationUpdateHelper._buildAttributeObject()<br>
*                                                          
* @param {String}pUid                                     <p>
*                                                         current Object uid<br>
*                                                          
* @param {Object}pAchievedScoresObject                    <p>
*                                                         achievedScoresObj we add ours to this<br>
*                                                         
* @param {Array[[][]]}pFieldDisplayObject                 <p>
*                                                         twodimensional array with value and key so we can get the displayValues<br>  
*                                                         
* @param {Object}pFilteredRecsObj                         <p>
*                                                         as returned by ClassificationUpdateHelper._buildFilteredRecsObject()<br>  
*                                                         
* @param {Object}pClassificationTypesNoFilterObj          <p>
*                                                         as built by ClassificationUpdateHelper._buildFilterAndNoFilterObj()<br>  
*                                                          
* @param {String}pCurrentObjectType                       <p>
*                                                         current Object Type<br>
*                                                          
* @return {Object}                                        <p>pAchievedScoresObject;
*/
ClassificationUpdateHelper._handleAttributes = function(pClassificationTypes, attributeObj, pUid, pAchievedScoresObject, pFieldDisplayObject, pFilteredRecsObj, pClassificationTypesNoFilterObj, pCurrentObjectType)
{
    var classificationTypes = pClassificationTypes;
    var currentAttributes = attributeObj[pUid];

    for(var type in classificationTypes)
    {
        if(ClassificationUpdateHelper._isRelevant(type, pUid, pClassificationTypes[type]["filter"], pFilteredRecsObj, pClassificationTypesNoFilterObj, pCurrentObjectType))
        {
            ClassificationUtils.validateUserSetValues(classificationTypes[type], true);
            if (!pAchievedScoresObject.hasOwnProperty(pUid))
            {
                pAchievedScoresObject[pUid] = {};
            }
            if (!pAchievedScoresObject[pUid].hasOwnProperty(classificationTypes[type]["classificationGroupId"]))
            {
                pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]] = {};
            }
            if (!pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]].hasOwnProperty(type))
            {
                pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type] = {};
                pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["points"] = 0;
                pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["key"] = "-";
                pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["value"] = "-";
                pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["maxPoints"] = classificationTypes[type]["maxPercent"] / 100 * classificationTypes[type]["points"];
                pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["fieldDisplay"] = ClassificationUtils.getFieldDisplayValue(pFieldDisplayObject, pClassificationTypes[type]["field"]);
            }

            for (var filter in classificationTypes[type]["values"])
            {
                for (var attribute in currentAttributes)
                {
                    if (classificationTypes[type]["fieldType"] == $ClassificationFieldTypes.NUMBER() || classificationTypes[type]["fieldType"] == $ClassificationFieldTypes.INTEGER())
                    {
                        var numberValue = ClassificationUpdateHelper._getCorrectNumberValue(currentAttributes[classificationTypes[type]["field"]], pClassificationTypes[type]);

                        pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type] = {};
                        pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["key"] = numberValue["key"];
                        pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["value"] = numberValue["value"];
                        pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["maxPoints"] = numberValue["maxPoints"];
                        pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["points"] = numberValue["points"];
                        pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["fieldDisplay"] = ClassificationUtils.getFieldDisplayValue(pFieldDisplayObject, pClassificationTypes[type]["field"]);
                    }
                    else
                    {
                        var currentValue = JSON.parse(filter)["filter"]["childs"][0]["key"];
                        for (var attributeValue in currentAttributes[attribute])
                        {
                            if (currentAttributes[attribute][attributeValue] == currentValue || currentAttributes[attribute][attributeValue] == parseInt(currentValue))
                            {
                                    pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type] = {};
                                    pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["points"] = classificationTypes[type]["values"][filter]*classificationTypes[type]["points"] / 100;
                                    pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["key"] = JSON.parse(filter)["filter"]["childs"]["0"]["key"];
                                    pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["value"] = JSON.parse(filter)["filter"]["childs"]["0"]["value"];
                                    pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["maxPoints"] = classificationTypes[type]["maxPercent"] / 100 * classificationTypes[type]["points"];
                                    pAchievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["fieldDisplay"] = ClassificationUtils.getFieldDisplayValue(pFieldDisplayObject, pClassificationTypes[type]["field"]);

                                //replace if the value is greater than the one we already have
                                if (parseInt(pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["points"]) < parseInt(classificationTypes[type]["values"][filter])*parseInt(classificationTypes[type]["points"]) / 100)
                                {
                                    pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["points"] = parseInt(classificationTypes[type]["values"][filter])*parseInt(classificationTypes[type]["points"]) / 100;
                                    pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["key"] = JSON.parse(filter)["filter"]["childs"]["0"]["key"];
                                    pAchievedScoresObject[pUid][classificationTypes[type]["classificationGroupId"]][type]["value"] = JSON.parse(filter)["filter"]["childs"]["0"]["value"];
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return pAchievedScoresObject;
};

/**
 * handles the entityfield indicators so you can add them to achievedScoresObject  <p><br>
 * 
 * @param {Object}pClassificationTypes                     <p>
 *                                                         as returned by buildindicatorObj()<br>
 *                                                         
 * @param {Object}pColumns                                 <p>
 *                                                         columns of the objectType<br>
 *                                                          
 * @param {Object}pRows                                    <p>
 *                                                         rows of the dataset<br>
 *                                                          
 * @param {String}pUid                                     <p>
 *                                                         current Object uid<br>
 *                                                          
 * @param {Object}pAchievedScoresObject                    <p>
 *                                                         achievedScoresObj we add ours to this<br>
 *                                                                                                           
 * @param {Array[[][]]}pFieldDisplayObject                 <p>
 *                                                         twodimensional array with value and key so we can get the displayValues<br>  
 *                                                         
 * @param {Object}pFilteredRecsObj                         <p>
 *                                                         as returned by ClassificationUpdateHelper._buildFilteredRecsObject()<br>  
 *                                                         
 * @param {Object}pClassificationTypesNoFilterObj          <p>
 *                                                         as built by ClassificationUpdateHelper._buildFilterAndNoFilterObj()<br>  
 *                                                          
 * @param {String}pCurrentObjectType                       <p>
 *                                                         current Object Type<br>
 *                                                          
 * @return {Object}                                        <p>pAchievedScoresObject;
 */
 ClassificationUpdateHelper._handleEntityFields = function(pClassificationTypes, pRows, pColumns, pUid, pAchievedScoresObject, pFieldDisplayObject, pFilteredRecsObj, pClassificationTypesNoFilterObj, pCurrentObjectType)
 {
     var achievedScoresObject = pAchievedScoresObject;
     var helperObject = {};
     for (var type in  pClassificationTypes)
     {
         if(ClassificationUpdateHelper._isRelevant(type, pUid, pClassificationTypes[type]["filter"], pFilteredRecsObj, pClassificationTypesNoFilterObj, pCurrentObjectType))
         {
             var valueSet = false;
             ClassificationUtils.validateUserSetValues(pClassificationTypes[type]);
             for (var filter in pClassificationTypes[type]["values"])
             {
                 if (!valueSet)
                 {
                     if (!helperObject.hasOwnProperty(pUid))
                     {
                         helperObject[pUid] = {};
                         achievedScoresObject[pUid] = {};
                     }
                     if (!helperObject.hasOwnProperty(pUid + pClassificationTypes[type]["classificationGroupId"]))
                     {
                         helperObject[pUid + pClassificationTypes[type]["classificationGroupId"]] = {};
                         achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]] = {};
                     }
                     if (!helperObject.hasOwnProperty(pUid + type))
                     {
                         helperObject[pUid + type] = {};
                         achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type] = 0;
                     }
                     if (pClassificationTypes[type]["fieldType"] == $ClassificationFieldTypes.NUMBER() || pClassificationTypes[type]["fieldType"] == $ClassificationFieldTypes.INTEGER())
                     {
                         var numberValue = ClassificationUpdateHelper._getCorrectNumberValue(false, pClassificationTypes[type], pColumns, pRows);
                         achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type] = {};
                         achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["key"] = numberValue["key"];
                         achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["value"] = numberValue["value"];
                         achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["maxPoints"] = numberValue["maxPoints"];
                         achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["points"] = numberValue["points"];
                         achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["fieldDisplay"] = ClassificationUtils.getFieldDisplayValue(pFieldDisplayObject, pClassificationTypes[type]["field"]);
                     }
                     else
                     {
                         if(achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["fieldDisplay"] == undefined)
                         {
                             achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type] = {};
                             achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["fieldDisplay"] = ClassificationUtils.getFieldDisplayValue(pFieldDisplayObject, pClassificationTypes[type]["field"]);
                         }

                         var filterValue = JditoFilterUtils.filterRecords(pColumns, [pRows], JSON.parse(filter).filter);
                         if (filterValue.length > 0)
                         {
                             achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["key"] = JSON.parse(filter)["filter"]["childs"]["0"]["key"];
                             achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["value"] = JSON.parse(filter)["filter"]["childs"]["0"]["value"];
                             achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["maxPoints"] = pClassificationTypes[type]["maxPercent"] / 100 * pClassificationTypes[type]["points"];
                             achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["points"] = 0;
                             achievedScoresObject[pUid][pClassificationTypes[type]["classificationGroupId"]][type]["points"] = parseInt(pClassificationTypes[type]["values"][filter]) * parseInt(pClassificationTypes[type]["points"]) / 100;
                             valueSet = true;
                         }
                     }
                 }
             }
         }
     }
     return achievedScoresObject;
 };
 
/**
* builds the indicatorObject, which stores all the classificationTypes we need with all their information like value, displayvalue, indicatortype, fieldType, maxpoints, max percents and their possible values and their percent  <p><br>
* 
*                                                         
* @param {Object}pCurrentObjectType                       <p>
*                                                         Object Type (needed, altough we have the object table, since organisation as example uses contact as it's table<br>
*                                                          
* @return {Object}                                        <p>indicatorObj[indicatorType][typeId] = {"classificationGroupId": classificationGroupId
*                                                                           , "fieldType": fieldType
*                                                                           , "field": field
*                                                                           , "maxPercent": maxPercent
*                                                                           , "points": points
*                                                                           , "filter": filter
*                                                                           , "values": {value: percent}
*/
ClassificationUpdateHelper._buildindicatorObj = function(pCurrentObjectType)
{
    var currentObjectType = pCurrentObjectType;
    var indicatorObj = {};
    var helperObj2 = {};
    indicatorObj[$ClassificationIndicatorTypes.ENTITYFIELD()] = {};
    indicatorObj[$ClassificationIndicatorTypes.ATTRIBUTE()] = {};
    indicatorObj[$ClassificationIndicatorTypes.COMPLEX()] = {};


    var classificationTypeScorePoints = newSelect("CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID, CLASSIFICATIONTYPE.CLASSIFICATIONTYPEID, CLASSIFICATIONSCORE.CLASSIFICATIONSCOREID\n\
                                                    , CASE WHEN CLASSIFICATIONSCORE.INDICATORTEXT is not null THEN CLASSIFICATIONSCORE.INDICATORTEXT ELSE \n\
                                                        CASE WHEN CLASSIFICATIONSCORE.INDICATORNUMBER is not null THEN CLASSIFICATIONSCORE.INDICATORNUMBER ELSE  \n\
                                                            CASE WHEN CLASSIFICATIONSCORE.INDICATORINTEGER is not null THEN CLASSIFICATIONSCORE.INDICATORINTEGER ELSE \n\
                                                                CASE WHEN CLASSIFICATIONSCORE.FIELDVALUE is not null THEN CLASSIFICATIONSCORE.FIELDVALUE ELSE null \n\
                                                                end\n\
                                                            end\n\
                                                        end\n\
                                                      END\n\
                                                        , CLASSIFICATIONTYPE.SCOREPOINTS, CLASSIFICATIONTYPE.FIELD, CLASSIFICATIONTYPE.FIELDTYPE \n\
                                                        , CLASSIFICATIONTYPE.INDICATORTYPE, CLASSIFICATIONSCORE.SCOREPERCENT, CLASSIFICATIONTYPE.SCOREPOINTS, CLASSIFICATIONTYPE.FILTER")
                                                    .from("CLASSIFICATIONSCORE")
                                                    .join("CLASSIFICATIONTYPE", "CLASSIFICATIONSCORE.CLASSIFICATIONTYPE_ID = CLASSIFICATIONTYPE.CLASSIFICATIONTYPEID")
                                                    .where("CLASSIFICATIONTYPE.OBJECT_TYPE", currentObjectType)
                                                    .orderBy("CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID, CLASSIFICATIONTYPE.CLASSIFICATIONTYPEID")
                                                    .table();
    for (let i = 0; i < classificationTypeScorePoints.length; i++)
    {
        var typeId = classificationTypeScorePoints[i][1];

        if (classificationTypeScorePoints[i][7] && classificationTypeScorePoints[i][6] != "")
        {
            if (!helperObj2.hasOwnProperty(typeId))
            {
                indicatorObj[classificationTypeScorePoints[i][7]][typeId] = {"classificationGroupId": classificationTypeScorePoints[i][0]
                                                                            , "fieldType": classificationTypeScorePoints[i][6]
                                                                            , "field": classificationTypeScorePoints[i][5]
                                                                            , "maxPercent": classificationTypeScorePoints[i][8]
                                                                            , "points": classificationTypeScorePoints[i][9]
                                                                            , "filter": classificationTypeScorePoints[i][10]
                                                                            , "values": {}
                                                                            };
                helperObj2[typeId] = "";
            }

            if (classificationTypeScorePoints[i][7] == $ClassificationIndicatorTypes.COMPLEX() && classificationTypeScorePoints[i][6] == $ClassificationFieldTypes.DATE()) // we don't use the actual value as the key for the dates, since they change daily
            {
                indicatorObj[classificationTypeScorePoints[i][7]][typeId]["values"][$ClassificationComplexIndicatorRegistry[classificationTypeScorePoints[i][5]]().getActualDateValueFn(classificationTypeScorePoints[i][3].toString())] = classificationTypeScorePoints[i][8];
            }
            else
            {
                indicatorObj[classificationTypeScorePoints[i][7]][typeId]["values"][classificationTypeScorePoints[i][3]] = classificationTypeScorePoints[i][8];
            }
            if (parseFloat(indicatorObj[classificationTypeScorePoints[i][7]][typeId]["maxPercent"]) < parseFloat(classificationTypeScorePoints[i][8]))
            {
                indicatorObj[classificationTypeScorePoints[i][7]][typeId]["maxPercent"] = classificationTypeScorePoints[i][8];
            }
        }
    }
    return indicatorObj;
};

/**
 * Builds filteredRecsObj Object which stores all the included Datasets to the specific filter.
 * (loops trough classificationTypesFilterObj and uses either entites.getRow or an select for every different filter)
 */
ClassificationUpdateHelper._buildFilteredRecsObject = function(pClassificationTypesFilterObj, pCurrentObjectType, pFilterFields)
{
    var currentObjectType = pCurrentObjectType;
    var filteredRecsObj = {}; //reset for every objectType
    var executeGetRows = true; //reset for every objectTyp

    for (var index in pClassificationTypesFilterObj[currentObjectType])
    {
        for (var indexIndex in pClassificationTypesFilterObj[currentObjectType][index]) 
        {
            var unparsedFilter = pClassificationTypesFilterObj[currentObjectType][index][indexIndex];
            var filter = JSON.parse(unparsedFilter).filter;
            //filteredRecsObj uses the filter as the key -> if multiple indicators use the exact same filtercondition we only have to get these rows once
            if (filteredRecsObj[pClassificationTypesFilterObj[currentObjectType][index][indexIndex]] == undefined)
            {
                if (filter["childs"]["0"] != undefined) // ignore empty filters
                {
                    //no "." in the name means its a field of the entity itself: use entities.getRow and JditoFilterUtils.filterRecords 
                    //to check whether or not the Filter excludes this row
                    if (!filter["childs"]["0"]["name"].includes(".")) 
                    {
                        if (executeGetRows) //only do this once for every object type, since the rows don't change depending on the filter
                        {
                            var loadConfig = entities.createConfigForLoadingRows()
                            .entity(ContextUtils.getEntity(currentObjectType))
                            .fields(pFilterFields);

                            var rows = entities.getRows(loadConfig);
                            var value;
                            var filterableRows = [];
                            for (let ii = 0; ii < rows.length; ii++) {
                                filterableRows[ii] = pFilterFields.map(function (field)
                                {
                                    value = null;
                                    if (field in rows[ii])
                                    {
                                        value = rows[ii][field];
                                    }
                                    return value;
                                });
                            }
                            executeGetRows = false;
                        }
                        //filter gets used after using entities.getRow and not in the loadconfig, since we can have multiple filters 
                        //and don't want to do entities.getRow for every single one 
                        filteredRecsObj[unparsedFilter] = JditoFilterUtils.filterRecords(pFilterFields, filterableRows, filter);
                    }
                    else //else: currently a count is made whith the filtercondition as the where clause --> poor performance
                    {
                        if (currentObjectType == "Organisation")
                        {
                            filteredRecsObj[unparsedFilter] = newSelect("CONTACT.CONTACTID")
                                            .from("ORGANISATION")
                                            .join("CONTACT", "ORGANISATION.ORGANISATIONID = CONTACT.ORGANISATION_ID and CONTACT.PERSON_ID is null")
                                            .leftJoin("ADDRESS", "ADDRESS.ADDRESSID = CONTACT.ADDRESS_ID")
                                            .leftJoin("CLASSIFICATIONSTORAGE", "CLASSIFICATIONSTORAGE.OBJECT_ROWID = CONTACT.CONTACTID")
                                            .where(db.toFilterCondition(JSON.stringify(filter), "Organisation_entity"))
                                            .and("ORGANISATION.ORGANISATIONID", OrgUtils.getPrivateOrganisationId(), SqlBuilder.NOT_EQUAL())
                                            .table();
                        }
                        else if (currentObjectType == "Salesproject")
                        {
                            filteredRecsObj[unparsedFilter] = newSelect("SALESPROJECT.SALESPROJECTID")
                                            .from("SALESPROJECT")
                                            .leftJoin("CONTACT", "SALESPROJECT.CONTACT_ID = CONTACT.CONTACTID")
                                            .leftJoin("ORGANISATION", "CONTACT.ORGANISATION_ID = ORGANISATION.ORGANISATIONID")
                                            .leftJoin("CLASSIFICATIONSTORAGE", "CLASSIFICATIONSTORAGE.OBJECT_ROWID = SALESPROJECT.SALESPROJECTID") 
                                            .where(db.toFilterCondition(JSON.stringify(filter), "Salesproject_entity"))
                                            .table();
                        }
                    }
                }
            }
        }
    }
    return filteredRecsObj;
};

/**
* Get's scoreArray and builds scoreObject, classificationTypesFilterObj and classificationTypesNoFilterObj and returns them in an object with their name as key<p><br>
* 
* @param {SqlBuilderCondition}pCorrectCondition          <p>
*                                                         condition of which classificationt storage datasets to get<br>
*                                                          
* @return {Object}gradingObject                          <p>
*                                                         gradingObject[objectType][classificationGroupId][classificationTypeId] = [minPercent, classificationGrading];<br>
*/
ClassificationUpdateHelper._buildGradingObject = function(pCorrectCondition)
{
    //All the gradings of the classification groups that have either 1: atleast one classificationType with the outdated flag or 2: have classificationtypes 
    //of the objectTypes that we need for updating for the outdated classificationStorage (depends on the condition, see above) 
    var gradingArray = newSelect("CLASSIFICATIONTYPE.OBJECT_TYPE, CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID, \n\
                                    CLASSIFICATIONGRADING.CLASSIFICATIONGRADINGID, CLASSIFICATIONGRADING.MINPERCENT, CLASSIFICATIONGRADING.GRADING")
                        .from("CLASSIFICATIONGROUP")
                        .join("CLASSIFICATIONTYPE", "CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID = CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID")
                        .where(pCorrectCondition)
                        .join("CLASSIFICATIONGRADING", "CLASSIFICATIONGRADING.CLASSIFICATIONGROUP_ID = CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID")
                        .orderBy("CLASSIFICATIONTYPE.OBJECT_TYPE, CLASSIFICATIONGROUP.TITLE, CLASSIFICATIONGROUP.SORTING, \n\
                                                        CLASSIFICATIONTYPE.SCORETYPE, CLASSIFICATIONGRADING.MINPERCENT asc")
                        .table();

    let gradingObject = {};
    let helperObject = {}; //resets the helper object

    let objectType, classificationGroupId, classificationTypeId, minPercent, classificationGrading;
    //logic from above applies here aswell
    for (let element in gradingArray)
    {
        [objectType, classificationGroupId, classificationTypeId, minPercent, classificationGrading] = gradingArray[element];
        if (!helperObject.hasOwnProperty(objectType)) //objectType
        {
            gradingObject[objectType] = {};
            helperObject[objectType] = "";
        }
        if (!helperObject.hasOwnProperty(classificationGroupId)) //classificationGroup
        {
            gradingObject[objectType][classificationGroupId] = {};
            helperObject[classificationGroupId] = "";
        }
        if (!helperObject.hasOwnProperty(classificationTypeId)) //classificationGrading
        {
            gradingObject[objectType][classificationGroupId][classificationTypeId] = [minPercent, classificationGrading]; //minPercent
            helperObject[classificationTypeId] = "";
        }
    }
    return gradingObject;
};

/**
* Get's scoreArray and builds scoreObject, classificationTypesFilterObj and classificationTypesNoFilterObj and returns them in an object with their name as key<p><br>
* 
* @param {Object}pCorrectCondition                        <p>
*                                                         condition of which classificationtypes to update<br>
*                                                          
* @param {Object}[pOutdatedStoredClassificationObjects]   <p>
*                                                         return of ClassificationUpdateHelper._buildOutdatedStoredClassificationObject().<br>
*                                                          
*                                                          
* @return {Object}                                          <p>
*                                                           If pSingleRefreshRowId is set: 
*                                                           {"scoreObject": object
*                                                            , "scoreArray": array
*                                                            , "classificationTypesFilterObj": object
*                                                            , "classificationTypesNoFilterObj": object}<br>
*/
ClassificationUpdateHelper._buildFilterAndNoFilterObj = function(pCorrectCondition, pOutdatedStoredClassificationObjects)
{
    let classificationTypesFilterObj = {};
    let classificationTypesNoFilterObj = {};
    //All the possible scores of 1: the outdated classification types or 2: of all the classificationtypes of the outdated classificationStorages
    let scoreArray = newSelect("CLASSIFICATIONTYPE.OBJECT_TYPE, CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID, CLASSIFICATIONTYPE.CLASSIFICATIONTYPEID, \n\
                                CLASSIFICATIONSCORE.CLASSIFICATIONSCOREID, CLASSIFICATIONSCORE.SCOREPERCENT/100, CLASSIFICATIONTYPE.FILTER")
                                        .from("CLASSIFICATIONGROUP")
                                        .join("CLASSIFICATIONTYPE", "CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID = CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID")
                                        .join("CLASSIFICATIONSCORE", "CLASSIFICATIONSCORE.CLASSIFICATIONTYPE_ID = CLASSIFICATIONTYPE.CLASSIFICATIONTYPEID")
                                        .where(pCorrectCondition)
                                        .orderBy("CLASSIFICATIONTYPE.OBJECT_TYPE, CLASSIFICATIONGROUP.TITLE, CLASSIFICATIONGROUP.SORTING, CLASSIFICATIONTYPE.SCORETYPE, CLASSIFICATIONSCORE.SORT")
                                        .table();
    var helperObject = {};
    var objectTypes = [];
    let objectType, classificationGroupId, classificationTypeId, classificationScoreId, classificationScore, classificationTypeFilter;
    //build scoreObject out of scoreArray
    for (let element in scoreArray)
    {
        [objectType, classificationGroupId, classificationTypeId, classificationScoreId, classificationScore, classificationTypeFilter] = scoreArray[element];
        if (!helperObject.hasOwnProperty(objectType)) //objectType
        {
            helperObject[objectType] = "";
            if (!pOutdatedStoredClassificationObjects)
            {
                objectTypes.push(objectType); //also push all the objectTypes in an Array for later (the updating happens one objectType at a time)
            }

            if (classificationTypesFilterObj[objectType] == undefined)
            {
                classificationTypesFilterObj[objectType] = {}; //initializing for later use
            }
            if (classificationTypesNoFilterObj[objectType] == undefined)
            {
                classificationTypesNoFilterObj[objectType] = {}; //initializing for later use
            }
        }
        if (!helperObject.hasOwnProperty(classificationGroupId)) //classificationGroup
        {
            helperObject[classificationGroupId] = "";

            if (classificationTypesFilterObj[objectType][classificationGroupId] == undefined)
            {
                classificationTypesFilterObj[objectType][classificationGroupId] = {}; //initializing 
            }
            if (classificationTypesNoFilterObj[objectType][classificationGroupId] == undefined)
            {
                classificationTypesNoFilterObj[objectType][classificationGroupId] = {}; //initializing 
            }
        }
        if (!helperObject.hasOwnProperty(classificationTypeId)) //classificationType
        {            
            helperObject[classificationTypeId] = "";

            if (classificationTypeFilter == "" || classificationTypeFilter == JditoFilterUtils.getEmptyFilter(ContextUtils.getEntity(objectType))) //also push all the indicators that have a filter set into classificationTypesFilterObj
            {
                if (classificationTypesNoFilterObj[objectType][classificationGroupId][classificationTypeId] == undefined)
                {
                    classificationTypesNoFilterObj[objectType][classificationGroupId][classificationTypeId] = classificationTypeFilter;
                }
            }
            else //and the ones without a filter into classificationTypesNoFilterObj
            {
                if (classificationTypesFilterObj[objectType][classificationGroupId][classificationTypeId] == undefined)
                {
                    classificationTypesFilterObj[objectType][classificationGroupId][classificationTypeId] = classificationTypeFilter;
                }
            }
        }
    }
    return {"classificationTypesFilterObj": classificationTypesFilterObj
            , "classificationTypesNoFilterObj": classificationTypesNoFilterObj};
};

/**
 *  Builds and returns the following Objects/Arrays and retuns them in an Object with their names as keys: <p>
 *      classificationTypesFilterObj: object, <p>
 *      classificationTypesNoFilterObj: object, <p>
 *      objectTypesThatNeedUpdate: array, <p>
 *   groupsThatNeedUpdate: array, <p><br>
 * 
 * @param {Boolean}[pRecalculateAll]                       <p>
 *                                                          If set: handles all the classificationTypes as outdated -> returns all groups, objectTypes and classificationTypes<br>
 *                                                          
 * @param {Boolean}[pSingleRefreshRowId]                    <p>
 *                                                          object row id of the dataset you want the classification for.<br>
 *                                                          If set: returns null for all the objects. We don't need any classificationTypes, instead we are getting the classification
 *                                                          using "ClassificationUpdateHelper._buildOutdatedStoredClassificationObject" as outdated so we are already updating it's 
 *                                                          classificationStorage dataset) 
 *                                                          
 *                                                          
 * @return {Object}                                         <p>
 *                                                          {classificationTypesFilterObj: object, <p>
 *                                                          classificationTypesNoFilterObj: object, <p>
 *                                                          objectTypesThatNeedUpdate: array, <p>
 *                                                          groupsThatNeedUpdate: array, }
 */
ClassificationUpdateHelper._buildClassificationFilterObjects = function(pRecalculateAll, pSingleRefreshRowId)
{
    let outdatedGroupsAndObjectTypes;
    if (pSingleRefreshRowId)
    {
        return {"classificationTypesFilterObj": null
                   ,"classificationTypesNoFilterObj": null
                   ,"objectTypesThatNeedUpdate": null
                   , "groupsThatNeedUpdate": null};
    }
    else if (pRecalculateAll)
    {
        outdatedGroupsAndObjectTypes = newSelect("CLASSIFICATIONTYPE.OBJECT_TYPE, CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID, CLASSIFICATIONTYPE.CLASSIFICATIONTYPEID, CLASSIFICATIONTYPE.FILTER, CLASSIFICATIONTYPE.FIELD, CLASSIFICATIONTYPE.FIELDTYPE, CLASSIFICATIONTYPE.INDICATORTYPE")
                                            .from("CLASSIFICATIONTYPE")
                                            .table();
    }
    else //all classificationgroups and their objectType that have atleast one classificationtype with the outdated flag set
    {
        outdatedGroupsAndObjectTypes = newSelect("CLASSIFICATIONTYPE.OBJECT_TYPE, CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID, CLASSIFICATIONTYPE.CLASSIFICATIONTYPEID, CLASSIFICATIONTYPE.FILTER, CLASSIFICATIONTYPE.FIELD, CLASSIFICATIONTYPE.FIELDTYPE, CLASSIFICATIONTYPE.INDICATORTYPE")
                                            .from("CLASSIFICATIONTYPE")
                                            .where("CLASSIFICATIONTYPE.OUTDATED", 1)
                                            .table();
    }

    let objectTypesThatNeedUpdate = [];
    let outdatedClassificationTypeObj = {};
    let classificationTypesFilterObj = {};
    let classificationTypesNoFilterObj = {};
    let groupsThatNeedUpdate = [];
    var helperObject = {};
    let objectType, classificationGroupId, classificationTypeId, classificationTypeFilter, classificationTypeField, classificationTypeFieldType, classificationTypeIndicatorType;
    for (let element in outdatedGroupsAndObjectTypes)
    {
        [objectType, classificationGroupId, classificationTypeId, classificationTypeFilter, classificationTypeField, classificationTypeFieldType, classificationTypeIndicatorType] = outdatedGroupsAndObjectTypes[element];
        if (!helperObject.hasOwnProperty(objectType)) //objectType
        {
            outdatedClassificationTypeObj[objectType] = {}; //puts objectType in classificationStorageObject and already initializes the object that later get's filled with the groups and their types
            objectTypesThatNeedUpdate.push(objectType);

            classificationTypesFilterObj[objectType] = {}; //initializing for later use
            classificationTypesNoFilterObj[objectType] = {}; //initializing for later use

            helperObject[objectType] = "";

        }
        if (!helperObject.hasOwnProperty(classificationGroupId)) //objectRowId
        {
            outdatedClassificationTypeObj[objectType][classificationGroupId] = {}; //same logic as before
            groupsThatNeedUpdate.push(classificationGroupId);

            classificationTypesFilterObj[objectType][classificationGroupId] = {}; //initializing 
            classificationTypesNoFilterObj[objectType][classificationGroupId] = {}; //initializing 

            helperObject[classificationGroupId] = "";
        }
        if (!helperObject.hasOwnProperty(classificationTypeId)) //objectRowId
        {
            outdatedClassificationTypeObj[objectType][classificationGroupId][classificationTypeId] = {"classificationTypeFilter": classificationTypeFilter}; //same logic as before

            if (classificationTypeFilter == "" || classificationTypeFilter == JditoFilterUtils.getEmptyFilter(ContextUtils.getEntity(objectType))) //also push all the indicators that have no filter set into classificationTypesNoFilterObj
            {
                classificationTypesNoFilterObj[objectType][classificationGroupId][classificationTypeId] = {};
                classificationTypesNoFilterObj[objectType][classificationGroupId][classificationTypeId] = {"classificationTypeFilter": classificationTypeFilter
                                                                                                            , "classificationTypeField": classificationTypeField
                                                                                                            , "classificationTypeFieldType": classificationTypeFieldType
                                                                                                            , "classificationTypeIndicatorType": classificationTypeIndicatorType};
            }
            else //and the ones with a filter into classificationTypesFilterObj
            {
                classificationTypesFilterObj[objectType][classificationGroupId][classificationTypeId] = {};
                classificationTypesFilterObj[objectType][classificationGroupId][classificationTypeId] = {"classificationTypeFilter": classificationTypeFilter
                                                                                                            , "classificationTypeField": classificationTypeField
                                                                                                            , "classificationTypeFieldType": classificationTypeFieldType
                                                                                                            , "classificationTypeIndicatorType": classificationTypeIndicatorType};
            }

            helperObject[classificationTypeId] = "";
        }
    }
    return {"classificationTypesFilterObj": classificationTypesFilterObj
            ,"classificationTypesNoFilterObj": classificationTypesNoFilterObj
            ,"objectTypesThatNeedUpdate": objectTypesThatNeedUpdate
            , "groupsThatNeedUpdate": groupsThatNeedUpdate};
};

/**
 *  Builds and returns the following Objects/Arrays and retuns them in an Object with their names as keys: <p>
 *      objectRowIdArray: array of ObjectRowIds <p>
 *      objectTypes: array of objectTypes<p>
 *      doneSelecting: boolean used to know when to stop (This function is used in a loop because it's only selecting a set amount of data (paging)<p><br>
 * 
 * @param {Boolean}[pRecalculateAll]                       <p>
 *                                                          If set: returns null for all the objects (instead we are getting all the classificationtypes 
 *                                                          using "ClassificationUpdateHelper._buildClassificationFilterObjects" as outdated so we are already updating all the 
 *                                                          classificationStorage datasets that exist)<br>
 *                                                          
 * @param {Boolean}[pSingleRefreshRowId]                   <p>
 *                                                          object row id of the dataset you want the classification for.<br>
 *                                                          If set: returns the object but with only it's object in there.<br>
 *                                                          
 * @param {Boolean}[pObjectType]                           <p>
 *                                                          The Object type of the object row id. (Mandatory if pSingleRefreshRowId is set!)<br>
 *                                                          This is normally just the Context name<br>
 *                                                          
 * @return {Object}                                          <p>
 *                                                           {objectRowIdArray: array, <p>
 *                                                          objectTypes: array, <p>
 *                                                          doneSelecting: boolean}
 */
ClassificationUpdateHelper._buildOutdatedStoredClassificationObject = function(pRecalculateAll, pSingleRefreshRowId, pObjectType)
{
    var doneSelecting = false;
    if (pRecalculateAll)
    {
        return {"objectRowIdArray": null
                , "objectTypes": null
                , "doneSelecting": false};
    }
    else
    {
        var outdatedStoredClassifications = [];
        var objectTypes = [];
        var objectRowIdArray = [];

        if (pSingleRefreshRowId)
        {
            outdatedStoredClassifications = newSelect("CLASSIFICATIONSTORAGE.OBJECT_TYPE, CLASSIFICATIONSTORAGE.OBJECT_ROWID")
                                                    .from("CLASSIFICATIONSTORAGE")
                                                    .where("CLASSIFICATIONSTORAGE.OBJECT_ROWID", pSingleRefreshRowId)
                                                    .orderBy("CLASSIFICATIONSTORAGE.OBJECT_TYPE, CLASSIFICATIONSTORAGE.OBJECT_ROWID")
                                                    .table();

            if (outdatedStoredClassifications.length == 0) //insert the classificationString if none exists
            {
                let amountOfGroups = newSelect("CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID")
                                            .from("CLASSIFICATIONTYPE")
                                            .where("CLASSIFICATIONTYPE.OBJECT_TYPE", pObjectType)
                                            .groupBy("CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID").arrayColumn().length;
                var newString = "";
                for (let i = 0; i < amountOfGroups; i++)
                {
                    newString = newString + "-";
                }
                outdatedStoredClassifications = [[pObjectType, pSingleRefreshRowId]];
                db.insertData("CLASSIFICATIONSTORAGE", ["CLASSIFICATIONSTORAGEID"
                                                        , "CLASSIFICATIONVALUE"
                                                        , "OBJECT_ROWID"
                                                        , "OBJECT_TYPE"
                                                        , "OUTDATED"
                                                        ], null, [util.getNewUUID()
                                                                    , newString
                                                                    , pSingleRefreshRowId
                                                                    , pObjectType
                                                                    , 1
                                                                    ]);
            }

        }
        else
        {
            outdatedStoredClassifications = newSelect("CLASSIFICATIONSTORAGE.OBJECT_TYPE, CLASSIFICATIONSTORAGE.OBJECT_ROWID")
                                                        .from("CLASSIFICATIONSTORAGE")
                                                        .where("CLASSIFICATIONSTORAGE.OUTDATED", 1)
                                                        .pageSize(400)
                                                        .orderBy("CLASSIFICATIONSTORAGE.OBJECT_TYPE, CLASSIFICATIONSTORAGE.OBJECT_ROWID")
                                                        .nextTablePage();
        }

        if (outdatedStoredClassifications.length != 5)
        {
            doneSelecting = true;
        }

        let helperObject = {};                                      
        if (outdatedStoredClassifications)
        {
            let objectType, objectRowId;
            //build objectTypes out of outdatedStoredClassifications
            for (let element in outdatedStoredClassifications)
            {
                [objectType, objectRowId] = outdatedStoredClassifications[element];
                if (!helperObject.hasOwnProperty(objectType)) //objectType
                {
                    helperObject[objectType] = "";
                    objectTypes.push(objectType); //also push all the objectTypes in an Array for later (the updating happens one objectType at a time)

                }
                if (!helperObject.hasOwnProperty(objectRowId)) //objectRowId
                {
                    helperObject[objectRowId] = "";
                    objectRowIdArray.push(objectRowId); //also push all the objectTypes in an Array for later
                }
            }
        }
        return {"objectRowIdArray": objectRowIdArray
                , "objectTypes": objectTypes
                , "doneSelecting": doneSelecting};
    }
};


/**
* Adds pTableName to every column in pColumnArray (with  a dot "." inbetween the tablename and the columnname)<p><br>
* 
* @param {Array}pColumnArray                              <p>
*                                                         Array of columns<br>
*                                                          
* @param {String}pTableName                               <p>
*                                                         Name of the table you want to add<br>
*                                                                                                      
* @return {Array}                                         <p>
*                                                         pColumnArray with the added tablename for each element<br>
*/
ClassificationUpdateHelper._addTablenameToColumns = function(pColumnArray, pTableName)
{
    let columnArray = Utils.clone(pColumnArray);
    for (let i = 0; i < columnArray.length; i++)
    {
        columnArray[i] = pTableName + "." + columnArray[i];
    }

    return columnArray;
};

/**
 * Checks whether or not the classificationType is relevant for the current dataset, 
 * (loops trough filteredRecsObj to check whether or not the filter excluded the dataset)
 * returns true/false
 * 
* @param {Object}pUid                                    <p>
*                                                         Uid of the dataset (needed, because pClassificationTypesFilter uses the objectUids<br>
*                                                          
* @param {String}pFilter                                 <p>
*                                                         filter of the classification type dataset<br>
*                                                         
* @param {Object}pClassificationTypesFilter              <p>
*                                                         Object with the filter as key and the uids of the included datasets as values. As built by ClassificationUpdateHelper._buildFilteredRecsObject()<br>
*                                                          
*                                                          
* @return {Boolean}                                      <p>
*                                                        If relevant: true, else: false<br>
*                                                            
*/
ClassificationUpdateHelper._isRelevantFilter = function(pUid, pFilter, pClassificationTypesFilter)
{
    var include = false;
    var recs = pClassificationTypesFilter[pFilter];
    if (recs != undefined)
    {
        include = recs.some(function (rec) {return rec[0] == pUid});
    }
    return include;
}

/**
 * Checks whether or not the classificationType is relevant for the current dataset 
 * (looping trough pClassificationTypesNoFilter if it's included there: no filter is set and it's relevant, 
 * othwerwises uses ClassificationUpdateHelper.ClassificationUpdateHelper._isRelevantFilter() to check whether or not it get's excluded by the set filter)
 * returns true/false
* @param {Object}pTypeId                                 <p>
*                                                         classification type id whose relevance is being checked<br>
*                                                          
* @param {Object}pUid                                    <p>
*                                                         Uid of the dataset (needed, because pClassificationTypesFilter uses the objectUids<br>
*                                                          
* @param {String}pFilter                                 <p>
*                                                         filter of the classification type dataset<br>
*                                                         
* @param {Object}pClassificationTypesFilter              <p>
*                                                         Object with the filter as key and the uids of the included datasets as values. As built by ClassificationUpdateHelper._buildFilteredRecsObject()<br>
*                                                         
* @param {Object}pClassificationTypesNoFilter            <p>
*                                                         Object with objectType, classificationgroup and it's classificationType Ids underneath. As built by ClassificationUpdateHelper._buildFilterAndNoFilterObj()<br>
*                                                         
* @param {String}pCurrentObjectType                      <p>
*                                                         object type (e.g: Organisation)<br>
*                                                          
* @return {Boolean}                                      <p>
*                                                        If relevant: true, else: false<br>
*                                                            
*/
ClassificationUpdateHelper._isRelevant = function(pTypeId, pUid, pFilter, pClassificationTypesFilter, pClassificationTypesNoFilter, pCurrentObjectType)
{
    var include = false;

    if (pFilter == "" || pFilter == undefined)
    {
        for (var groupId in pClassificationTypesNoFilter[pCurrentObjectType])
        {
            for (var typeId in pClassificationTypesNoFilter[pCurrentObjectType][groupId])
            {
                if (typeId == pTypeId)
                {    
                    include = true;
                    break;
                }
            }
        }
    }
    else
    {
        include = ClassificationUpdateHelper._isRelevantFilter(pUid, pFilter, pClassificationTypesFilter);
    }
    return include;
};

/**
* Calculates the classifications for the parsed Object and updates them if needed.<p><br>
* 
* @param {Object}[pClassificationObjects]                 <p>
*                                                         return of ClassificationUpdateHelper._buildClassificationFilterObjects()<br>
*                                                          
* @param {Object}[pOutdatedStoredClassificationObjects]   <p>
*                                                         return of ClassificationUpdateHelper._buildOutdatedStoredClassificationObject().<br>
*                                                          
* @param {String}[pSingleRefreshRowId]                    <p>
*                                                         object row id of the dataset you want the classification for.<br>
*                                                         
* @param {String}[pOutputInfo]                            <p>
*                                                         The output Info as string, so we can concat the new on onto it.<br>
*                                                          
*                                                          
* @return {Object}                                          <p>
*                                                           If pSingleRefreshRowId is set: 
*                                                           {"achievedScoresObject": object
*                                                            , "indicatorObj": object
*                                                            , "orderedGroups": array
*                                                            , "newGradingString": String}<br>
*                                                           else:
*                                                           {"outputInfo": object}
*                                                            
*/
ClassificationUpdateHelper._updateOutdatedDatasets = function(pClassificationObjects, pOutdatedStoredClassificationObjects, pSingleRefreshRowId, pOutputInfo)
{
    var correctCondition;
    var objectTypes = [];
    if (pClassificationObjects) //if the param exists: assign values accordingly
    {
        var classificationTypesFilterObj = pClassificationObjects["classificationTypesFilterObj"];
        var classificationTypesNoFilterObj = pClassificationObjects["classificationTypesNoFilterObj"];
        var objectTypesThatNeedUpdate = pClassificationObjects["objectTypesThatNeedUpdate"];
        var groupsThatNeedUpdate = pClassificationObjects["groupsThatNeedUpdate"];

        objectTypes = objectTypesThatNeedUpdate;
        

        correctCondition = newWhere("CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID", groupsThatNeedUpdate, SqlBuilder.IN());//and set condition accordingly
    }
    else if (pOutdatedStoredClassificationObjects) //if the param exists: assign values accordingly
    {
        var objectRowIdArray = pOutdatedStoredClassificationObjects["objectRowIdArray"];
        objectTypes = pOutdatedStoredClassificationObjects["objectTypes"];
        correctCondition = newWhere("CLASSIFICATIONTYPE.OBJECT_TYPE", objectTypes, SqlBuilder.IN());//and set condition accordingly
    }

    var filterObjects = ClassificationUpdateHelper._buildFilterAndNoFilterObj(correctCondition, pOutdatedStoredClassificationObjects);

    classificationTypesNoFilterObj = filterObjects["classificationTypesNoFilterObj"];
    classificationTypesFilterObj = filterObjects["classificationTypesFilterObj"];

    if (!pOutdatedStoredClassificationObjects) 
    {
        correctCondition = newWhere("CLASSIFICATIONTYPE.OBJECT_TYPE", objectTypesThatNeedUpdate, SqlBuilder.IN());
    }

    var gradingObject = ClassificationUpdateHelper._buildGradingObject(correctCondition);

    if (!pOutdatedStoredClassificationObjects) //Important to do this after ClassificationUpdateHelper._buildGradingObject! 
    {
        correctCondition = newWhere("CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID", groupsThatNeedUpdate, SqlBuilder.IN());
    }

    var outputInformation = {};
    var fieldDisplayObject = {};
    
    for (var currentObjectType in objectTypes) //update for each objectType
    {
        var isOrganisation = false;
        var currentObjectTable;
        var currentObjectColumn;
        if (objectTypes[currentObjectType] == "Organisation")
        {
            currentObjectTable = "CONTACT";
            currentObjectColumn = "CONTACT.CONTACTID";
            isOrganisation = true;
        }
        else
        {
            currentObjectTable =  objectTypes[currentObjectType];
            currentObjectColumn = objectTypes[currentObjectType] + "." + objectTypes[currentObjectType] +"ID";
        }

        outputInformation[objectTypes[currentObjectType]] = {
                updatedElements: 0,
                nonChangedElements: 0
            };
        
        var filterFields = [];
        if (isOrganisation)
        {
            filterFields = ["CONTACTID", "CUSTOMERCODE", "INFO", "LANGUAGE", "NAME", "STANDARD_CITY", "STANDARD_EMAIL_COMMUNICATION", "STANDARD_PHONE_COMMUNICATION", "STATUS", "TYPE"];
        }
        else if (currentObjectTable == "Salesproject")
        {
            filterFields = ["SALESPROJECTID", "CONTACT_ID", "ENDDATE", "INFO", "PHASE", "PROBABILITY", "PROJECTCODE", "PROJECTTITLE", "REASONS", "STARTDATE", "STATUS", "VOLUME"];
        }
        else
        {
            throw new Error("updateClassifications_serverProcess: objectType doesn't exist or is not implemented correctly, contact an administrator.");
        }

        //get an two dimensional array with all the values and displayvalues and store it in fieldDisplayObject with the objectType as key:
        //e.g: {"Organisation":[["STATUS","Status"],["TYPE","Typ"],["eyJpZCI6IjU0N2I4YjlkLTg4YmEtNDU5MC05ZTAxLTM0ZDJhNTgxMTZjYyIsInR5cGUiOiJDT01CTyJ9","Besuchsplanung / Besuchsfrequenz"]...]}
        fieldDisplayObject[objectTypes[currentObjectType]] = ClassificationUtils.getEntityFields(objectTypes[currentObjectType])
                    .concat(ClassificationUtils.getEntityAttributesArray(ClassificationUtils.getEntityAttributes(objectTypes[currentObjectType])))
                    .concat(ClassificationComplexIndicatorRegistryUtils.getComplexIndicatorArray(objectTypes[currentObjectType]));

        //private function for code readability (get's called once for every objectType)
        var filteredRecsObj = ClassificationUpdateHelper._buildFilteredRecsObject(classificationTypesFilterObj, objectTypes[currentObjectType], filterFields);


        //all groups of the objectType in the correct order, needed later to also add the "-" gradings if no classificationType has been set under that group
        orderedGroups = newSelect("distinct CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID, CLASSIFICATIONGROUP.SORTING")
                                    .from("CLASSIFICATIONGROUP")
                                    .join("CLASSIFICATIONTYPE", "CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID = CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID")
                                    .where("CLASSIFICATIONTYPE.OBJECT_TYPE", objectTypes[currentObjectType])
                                    .orderBy("CLASSIFICATIONGROUP.SORTING")
                                    .table();

        if (pSingleRefreshRowId)
        {
            correctCondition = newWhere(currentObjectColumn, pSingleRefreshRowId);
        }
        else if (pOutdatedStoredClassificationObjects)
        {
            correctCondition = newWhere(currentObjectColumn, objectRowIdArray, SqlBuilder.IN());
        }
        else
        {
            correctCondition = newWhere("CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID", groupsThatNeedUpdate, SqlBuilder.IN());
        }

        //get Columns
        var objectColumns = db.getColumns(String.toUpperCase(objectTypes[currentObjectType]), db.getCurrentAlias());
        //add tablename
        var objectColumnsWithTablename = ClassificationUpdateHelper._addTablenameToColumns(objectColumns, String.toUpperCase(objectTypes[currentObjectType]));

        if (isOrganisation)
        {
            //get Columns
            var contactColumns = db.getColumns(String.toUpperCase("CONTACT"), db.getCurrentAlias());
            //add tablename
            objectColumnsWithTablename = objectColumnsWithTablename.concat(ClassificationUpdateHelper._addTablenameToColumns(contactColumns, "CONTACT"));
        }

        var indicatorObj = ClassificationUpdateHelper._buildindicatorObj(objectTypes[currentObjectType]);

        var startRow = 0;
        var stop = false;
        var entityFields = ClassificationUpdateHelper._getEntityFields(objectTypes[currentObjectType]);

        var orderedGroups = newSelect("distinct CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID, CLASSIFICATIONGROUP.SORTING")
                                    .from("CLASSIFICATIONGROUP")
                                    .join("CLASSIFICATIONTYPE", "CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID = CLASSIFICATIONGROUP.CLASSIFICATIONGROUPID")
                                    .where("CLASSIFICATIONTYPE.OBJECT_TYPE", objectTypes[currentObjectType])
                                    .orderBy("CLASSIFICATIONGROUP.SORTING")
                                    .table();

        var achievedScoresObject = {};
        var pagingSize = 400;//in variable so there is only one place to edit
        //page entities.getRows instead of getting all at once
        while(!stop)
        {
            var objectValuesLoadConfig = entities.createConfigForLoadingRows()
                                                    .fields(entityFields)
                                                    .ignorePermissions(true)
                                                    .entity(ContextUtils.getEntity(objectTypes[currentObjectType]));
            if (objectRowIdArray && !pSingleRefreshRowId)//objectRowIdArray is already paged
            {
                objectValuesLoadConfig = objectValuesLoadConfig.uids(objectRowIdArray);
                stop = true;
            }
            else
            {
                objectValuesLoadConfig = objectValuesLoadConfig
                                                    .startrow(startRow)
                                                    .count(pagingSize);

                if (pSingleRefreshRowId)
                {
                    objectValuesLoadConfig = objectValuesLoadConfig.uid(pSingleRefreshRowId);
                }
            }
            var rows = entities.getRows(objectValuesLoadConfig);

            if (rows.length == pagingSize)
            {
                startRow = eMath.addInt(startRow, pagingSize);
            }
            else
            {
                stop = true;
            }
            //helper Function for better code-readability
            var processValues = ClassificationUpdateHelper._processObjectValuesBatchFn(rows, indicatorObj, achievedScoresObject, gradingObject[objectTypes[currentObjectType]], orderedGroups, objectTypes[currentObjectType], outputInformation, fieldDisplayObject, filteredRecsObj, classificationTypesNoFilterObj);
            achievedScoresObject = processValues["achievedScoresObject"];
            outputInformation = processValues["outputInformation"];

            if (pSingleRefreshRowId)
            {
                return {"achievedScoresObject": achievedScoresObject
                        , "indicatorObj": indicatorObj
                        , "orderedGroups": orderedGroups
                        , "newGradingString": processValues["newGradingString"]};
            }
        }
    }

    var outputInfo = pOutputInfo  + JSON.stringify(outputInformation) + "\n";

    if (groupsThatNeedUpdate)
    {
        var cond = newWhere("CLASSIFICATIONTYPE.CLASSIFICATIONGROUP_ID", groupsThatNeedUpdate, SqlBuilder.IN());
        cond.updateData(true, "CLASSIFICATIONTYPE", ["OUTDATED"], null, [0]);
    }
    return {"outputInfo": outputInfo};
};