Something went wrong on our end
-
[Projekt: xRM-Sales][TicketNr.: 1077022][Vertriebsprojekt Eigenschaften können mehrfach ausgewählt werden.]
[Projekt: xRM-Sales][TicketNr.: 1077022][Vertriebsprojekt Eigenschaften können mehrfach ausgewählt werden.]
process.js 81.48 KiB
import("Util_lib");
import("Employee_lib");
import("KeywordData_lib");
import("Context_lib");
import("system.util");
import("system.datetime");
import("system.translate");
import("system.neon");
import("system.vars");
import("system.db");
import("system.project");
import("system.entities");
import("Sql_lib");
import("Keyword_lib");
/**
* Provides functions for the work with attributes,<br>
* like listing the available attributes for a context.<br>
* <b><u>Don't instanciate this!</u></b>
*
* @class
*/
function AttributeUtil () {}
/**
* Returns all possible usageable contexts for attributes.
* <p>
* @return {Array} Array with the useable contexts.
*/
AttributeUtil.getPossibleUsageContexts = function()
{
return [
"Organisation",
"Person",
"Contract",
"Product",
"Activity",
"Offer",
"Order",
"Employee",
"Salesproject",
"Campaign",
"DocumentTemplate",
"SupportTicket",
"Leadimport",
"ImportField"
];
}
/**
* Gives an array of all available attributes for a context.<br>
* This is used in the possibleItems process for the <br>
* attribute id in AttributeRelation.<br>
*
* @param {String} pObjectType <p>
* The object type (context).<br>
* @param {Boolean} pIncludeGroups=false (optional) <p>
* Description.<br>
* @param {String[]} pFilteredAttributeIds=[] (optional) <p>
* Whitleist of attribute ids.<br>
* @param {Object} pAttributeCount=null (optional) <p>
* Object with attribute ids and their count.<br>
* @return {String[]} <p>
* Array of attribute ids.
*/
AttributeUtil.getPossibleAttributes = function (pObjectType, pIncludeGroups, pFilteredAttributeIds, pAttributeCount)
{
if (pObjectType == null || (pFilteredAttributeIds && pFilteredAttributeIds.length == 0))
return [];
var attrSelect = newSelect("AB_ATTRIBUTEID, ATTRIBUTE_PARENT_ID, ATTRIBUTE_TYPE")
.from("AB_ATTRIBUTE")
.join("AB_ATTRIBUTEUSAGE", "AB_ATTRIBUTEID = AB_ATTRIBUTE_ID")
.where("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
.and("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.COMBOVALUE(), SqlBuilder.NOT_EQUAL())
.and("ATTRIBUTE_ACTIVE = 1");
if (pAttributeCount)
{
for (let attributeId in pAttributeCount)
{
attrSelect.and(newWhere()
.or("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", attributeId, SqlBuilder.NOT_EQUAL())
.or("AB_ATTRIBUTEUSAGE.MAX_COUNT", pAttributeCount[attributeId], SqlBuilder.GREATER())
.or("AB_ATTRIBUTEUSAGE.MAX_COUNT is null")
);
}
}
if (pFilteredAttributeIds)
{
var filteredIdChildren = AttributeUtil.getAllChildren(pFilteredAttributeIds);
var allFilteredIds = pFilteredAttributeIds.concat(filteredIdChildren);
attrSelect.andIfSet("AB_ATTRIBUTE.AB_ATTRIBUTEID", allFilteredIds, SqlBuilder.IN())
}
if (!pIncludeGroups)
attrSelect.and("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.GROUP(), SqlBuilder.NOT_EQUAL());
var attributes = attrSelect.table();
//filter out groups without usable children
if (pIncludeGroups && pAttributeCount)
{
var parentIds = {};
attributes.forEach(function (attribute)
{
this[attribute[1]] = true;
}, parentIds);
attributes = attributes.filter(function (attribute)
{
return attribute[2].trim() != AttributeTypes.GROUP() || this[attribute[0]];
}, parentIds);
}
return attributes.map(function (attribute)
{
return attribute[0];
});
}
/**
* Searches for possible values for a atttribute<br>
* and returns these. The values depend on<br>
* the attributeType.<br>
*
* @param {String} pAttributeId <p>
* The id of the attribute.<br>
* @param {Boolean} pAttributeType <p>
* Type of the attribute that is <br>
* specified with pAttributeId. The type <br>
* needs to be passed to the function <br>
* for better performance. (loading the<br>
* type via attribute several times would<br>
* be too slow)<br>
* @param {Boolean} pIncludeInactives=false (optional) <p>
* Specifies if only active attributevalues <br>
* or actives and inactives shall be returned,<br>
* this is important when you want <br>
* to search for attribute values.<br>
* @return {Array} <p>
* 2D-array with [id, value] as elements if<br>
* the given attributeType has possible items,<br>
* if not null is returned.<br>
*/
AttributeUtil.getPossibleListValues = function (pAttributeId, pAttributeType, pIncludeInactives)
{
var attributeId = pAttributeId;
var attrType = pAttributeType.trim();
var onlyActives = !pIncludeInactives;
if (attrType == AttributeTypes.COMBO())
{
var valuesSelect = newSelect("AB_ATTRIBUTEID, ATTRIBUTE_NAME")
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", attributeId)
.and("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.COMBOVALUE());
if (onlyActives)
valuesSelect.and("AB_ATTRIBUTE.ATTRIBUTE_ACTIVE", "1");
var valueList = valuesSelect.orderBy("SORTING asc")
.table();
for (let i = 0; i < valueList.length; i++)
{
valueList[i][1] = translate.text(valueList[i][1]);
}
return valueList;
}
else if (attrType == AttributeTypes.BOOLEAN())
{
return [
["1", translate.text("Yes")],
["0", translate.text("No")]
];
}
else if (attrType == AttributeTypes.KEYWORD())
{
var attrKeyword = newSelect("DROPDOWNDEFINITION")
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.AB_ATTRIBUTEID", attributeId)
.cell();
var keywords = KeywordData.getSimpleData(attrKeyword, null, onlyActives);
return keywords;
}
else if (attrType == AttributeTypes.OBJECTSELECTION())
{
var [module, filter] = newSelect("DROPDOWNDEFINITION, DROPDOWNFILTER")
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.AB_ATTRIBUTEID", attributeId)
.arrayRow();
var objects = [];
if (module)
{
var uid = "#UID";
if (module == "Employee_entity")
uid = "SHORT_UID";
var title = "#CONTENTTITLE";
var config = entities.createConfigForLoadingRows()
.entity(module)
.fields([uid, title]);
if (filter)
{
filter = JSON.parse(filter);
if (filter.filter)
config.filter(JSON.stringify(filter.filter));
}
var rows = entities.getRows(config);
for (let i = 0, l = rows.length; i < l; i++)
objects.push([rows[i][uid], rows[i][title]])
}
return objects;
}
else
return null;
}
/**
* Returns the name of an attribute <br>
* with all parent attribute names.<br>
*
* @param {String} pAttributeId <p>
* The id of the attribute.<br>
* @param {Boolean} pSimpleName=false (optional) <p>
* Use only the name of the attribute <br>
* and not the names of the parents.<br>
* @param {Boolean} pTranslate=true (optional) <p>
* If the name should be translated.<br>
* @param {Number} pStartLayer=0 (optional) <p>
* Group names to omit. Example: The attribute <br>
* is "Departments / Distribution / Field staff".<br>
* If you set this value to 1, "Departments / " will <br>
* be removed, if set to 2, "Departments / Distribution /"<br>
* will be removed and so on. The last name will never <br>
* be removed to avoid an empty result <br>
* if something is wrong.<br>
* @return {String} <p>
* The name of the attribute.<br>
*/
AttributeUtil.getFullAttributeName = function (pAttributeId, pSimpleName, pTranslate, pStartLayer)
{
if (pSimpleName === undefined)
pSimpleName = false;
if (pTranslate === undefined)
pTranslate = true;
if (!pAttributeId)
return "";
if (pSimpleName)
return AttributeUtil.getSimpleAttributeName(pAttributeId, pTranslate);
var attributeNames = [];
var attribute;
do {
attribute = newSelect("ATTRIBUTE.ATTRIBUTE_NAME, PARENT1.ATTRIBUTE_NAME, PARENT2.ATTRIBUTE_NAME, PARENT2.ATTRIBUTE_PARENT_ID")
.from("AB_ATTRIBUTE", "ATTRIBUTE")
.leftJoin("AB_ATTRIBUTE", "ATTRIBUTE.ATTRIBUTE_PARENT_ID = PARENT1.AB_ATTRIBUTEID", "PARENT1")
.leftJoin("AB_ATTRIBUTE", "PARENT1.ATTRIBUTE_PARENT_ID = PARENT2.AB_ATTRIBUTEID", "PARENT2")
.where(["AB_ATTRIBUTE", "AB_ATTRIBUTEID", "ATTRIBUTE"], pAttributeId)
.arrayRow();
if (attribute.length > 0)
{
attributeNames.unshift(attribute[0]);
if (attribute[1])
attributeNames.unshift(attribute[1]);
if (attribute[2])
attributeNames.unshift(attribute[2]);
pAttributeId = attribute[3];
}
else
pAttributeId = "";
} while (pAttributeId);
if (pStartLayer)
attributeNames = attributeNames.slice(pStartLayer < attributeNames.length ? pStartLayer : -1);
if (pTranslate)
{
attributeNames = attributeNames.map(function (name)
{
return translate.text(name);
});
}
return attributeNames.join(" / ");
}
/**
* Returns the name of an attribute.
*
* @param {String} pAttributeId <p>
* The id of the attribute.<br>
* @param {boolean} pTranslate <p>
* If the name should be translated.<br>
* @return {String} <p>
* The name of the attribute.<br>
*/
AttributeUtil.getSimpleAttributeName = function (pAttributeId, pTranslate)
{
var attributeName = newSelect("ATTRIBUTE_NAME")
.from("AB_ATTRIBUTE")
.whereIfSet("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
.cell(true, "");
if (pTranslate)
attributeName = translate.text(attributeName);
return attributeName;
}
/**
* Returns the ids of all subordinated attributes of an attribute.
*
* @param {String|Array} pAttributeIds <p>
* The id(s) of the attribute(s).<br>
* @return {String[]} <p>
* Array with the ids of every subordinated attribute.<br>
*/
AttributeUtil.getAllChildren = function (pAttributeIds)
{
var childIds = [];
if (typeof(pAttributeIds) == "string")
pAttributeIds = [pAttributeIds];
while (pAttributeIds.length > 0)
{
pAttributeIds = newSelect("AB_ATTRIBUTEID")
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeIds, SqlBuilder.IN())
.arrayColumn();
if (pAttributeIds.length > 0)
childIds = childIds.concat(pAttributeIds);
}
return childIds;
}
/**
* Checks if an attribute has attribute relations.
*
* @param {String} pAttributeId <p>
* The id of the attribute.<br>
* @return {Boolean} <p>
* True, if it has relations.<br>
*/
AttributeUtil.hasRelations = function (pAttributeId)
{
if (!pAttributeId)
return false;
return new AttributeRelationQuery()
.attributeId(pAttributeId)
.getAttributeCount() != 0;
}
/**
* Returns the type of an attribute.
*
* @param {String} pAttributeId <p>
* The id of the attribute.<br>
* @return {String} <p>
* Attribute type.<br>
*/
AttributeUtil.getAttributeType = function (pAttributeId)
{
if (!pAttributeId)
return "";
return newSelect("ATTRIBUTE_TYPE")
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
.cell()
.trim();
}
/**
* Checks whether the given object type <br>
* has attribute in usage.<br>
*
* @param {String} pObjectType <p>
* The object type.<br>
* @return {String} <p>
* Returns false whether the given object<br>
* type is not filled correctly and true<br>
* if the given object type has attributes<br>
* in usage.<b>
*/
AttributeUtil.hasAttributes = function (pObjectType)
{
if (!pObjectType)
return false;
return newSelect("count(*)")
.from("AB_ATTRIBUTEUSAGE")
.join("AB_ATTRIBUTE", "AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID")
.where("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
.and("AB_ATTRIBUTE.ATTRIBUTE_ACTIVE", "1")
.and("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.COMBOVALUE(), SqlBuilder.NOT_EQUAL())
.and("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.GROUP(), SqlBuilder.NOT_EQUAL())
.cell() != "0"; //TODO: is there a way exists could be used?
}
/*********************************************************************************************************************/
/**
* Provides functions for the work with attributeRelations,<br>
* getting the value of an attributeRelation for an object.<br>
* <b>Don't instanciate this!</b>
*
* @class
*/
function AttributeRelationUtils () {}
/**
* @deprecated use AttributeRelationQuery
*
* gets the value of an attributeRelation for one dataset (e. g. a person)
*
* @param {String} pAttributeId <p>
* Attribute id.<br>
* @param {String} pObjectRowId <p>
* Row id of the dataset.<br>
* @param {String} pObjectType=null <p>
* Object type.<br>
* @param {String} pGetViewValue=false <p>
* If true, the values are resolved and formatted.<br>
* @param {String} pGetAttrname=false <p>
* If true, the attributename is also returned.<br>
* @return {String|String[]|null} <p>
* The value of the attribute or an array<br>
* of attrname and value [attrname, value]<br>
* (if pGetAttrname is true)<br>
*/
AttributeRelationUtils.getAttribute = function (pAttributeId, pObjectRowId, pObjectType, pGetViewValue, pGetAttrname)
{
var attributeQuery = new AttributeRelationQuery(pObjectRowId, pAttributeId, pObjectType);
if (pGetViewValue)
attributeQuery.includeDisplayValue();
if (pGetAttrname)
attributeQuery.includeFullAttributeName();
var attribute = attributeQuery.getSingleAttribute();
var value = pGetViewValue ? attribute.displayValue : attribute.value;
return pGetAttrName ? [attribute.fullAttributeName, value] : value;
}
/**
* Get a SqlBuilder already containing <br>
* the full select for attributes.<br>
*
* @param {String[]} pFields <p>
* Array of all fields which should be selected.<br>
* @param {String} pObjectRowId <p>
* Object row id.<br>
* @param {String} [pObjectType=null] <p>
* Object type.<br>
* @return {SqlBuilder} <p>
* A already filled SqlBuilder.<br>
*/
AttributeRelationUtils.getAttributeSqlBuilder = function (pFields, pObjectRowId, pObjectType)
{
return newSelect(pFields)
.from("AB_ATTRIBUTERELATION")
.join("AB_ATTRIBUTE", "AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID")
.leftJoin("AB_ATTRIBUTE COMBOVAL", AttributeTypes.COMBO.databaseField + " = COMBOVAL.AB_ATTRIBUTEID")
.whereIfSet("AB_ATTRIBUTERELATION.OBJECT_ROWID", pObjectRowId)
.andIfSet("AB_ATTRIBUTERELATION.OBJECT_TYPE", pObjectType);
}
/**
* @deprecated use AttributeRelationQuery
* Gets all attributes for a dataset.
*
* @param {String} pObjectRowId <p>
* Object row id.<br>
* @param {String} pObjectType=null <p>
* Object type.<br>
* @param {String} pUseAttributeIds=0 <p>
* <ul>
* <li>0: The full attribute names are returned.<br></li>
* <li>1: The ids are used instead of the full attribute names.<br></li>
* <li>2: The ids and the full attribute name is returned.<br></li>
* </ul>
* @param {String} pUseIdValues=false If true the values are not resolved or formatted<br>
* [attributeId, attributeName, value].<br>
* @return {String[][]} <p>
* Two-dimensional array a row is [attributeId|attributeName, value]<br>
* (or [attributeId, attributeName, value]).<br>
*/
AttributeRelationUtils.getAllAttributes = function (pObjectRowId, pObjectType, pUseAttributeIds, pUseIdValues)
{
var attributeQuery = new AttributeRelationQuery(pObjectRowId, pObjectType);
if (!pUseAttributeIds || pUseAttributeIds == 2)
attributeQuery.includeFullAttributeName();
if (!pUseIdValues)
attributeQuery.includeDisplayValue();
return attributeQuery.getAttributes().map(function (row)
{
var value = pUseIdValues ? row.value : row.displayValue;
switch (pUseAttributeIds)
{
case 1:
return [row.attributeId, value];
case 2:
return [row.attributeId, row.fullAttributeName, value];
case 0:
default:
return [row.fullAttributeName, value];
}
});
}
/**
* Gets the correct attribute value from <br>
* a map with values depending on the attribute id.<br>
*
* @param {String} pAttributeId <p>
* The attribute id.
* @param {Object} pValueMap <p>
* A map with the attribute values <br>
* and the db fields as keys.<br>
* @param {Boolean} pGetViewValue=false <p>
* If true, get the view value.<br>
* @return {String|null} <p>
* The value of the attribute or null <br>
* if the attribute doesn't exist.<br>
*/
AttributeRelationUtils.selectAttributeValue = function (pAttributeId, pValueMap, pGetViewValue)
{
var type = newSelect("ATTRIBUTE_TYPE, DROPDOWNDEFINITION")
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
.arrayRow();
if (!type.length)
return null;
type[0] = type[0].trim();
var field = AttributeTypeUtil.getDatabaseField(type[0]);
var value = pValueMap[field];
if(value == undefined)
return "";
if (pGetViewValue && type[0] == AttributeTypes.COMBO())
{
value = newSelect("ATTRIBUTE_NAME")
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.AB_ATTRIBUTEID", value)
.cell();
}
else if (pGetViewValue)
value = AttributeTypeUtil.getAttributeViewValue(type[0], value, type[1]);
return value;
}
/**
* @deprecated use AttributeRelationQuery.prototype.insertAttribute
*
* Inserts an attribute relation and validates if it can be used for the context and
* it also heeds the max usage count.
*
* @return {boolean} true, if the attribute relation was inserted
*/
AttributeRelationUtils.setAttribute = function (pRowId, pObjectType, pAttributeId, pValue)
{
return new AttributeRelationQuery(pRowId, pAttributeId, pObjectType)
.insertAttribute(pValue, false);
}
/**
* @deprecated use AttributeRelationQuery.prototype.insertAttribute
*
* inserts an attribute without validating the count
*/
AttributeRelationUtils.insertAttribute = function (pRowId, pObjectType, pAttributeId, pValue)
{
return new AttributeRelationQuery(pRowId, pAttributeId, pObjectType)
.insertAttribute(pValue, true);
}
/**
* Adds rows for attributes with min_count > 0.
*
* @param {String} pObjectType <p>
* The object type.<br>
* @param {String} pConsumer <p>
* The name of the attribute relation consumer.<br>
* @param {String[]} pFiltered <p>
* Array of attributeId's which act as a whitelist.<br>
* (groups are resolves to the childid's)<br>
*/
AttributeRelationUtils.presetMandatoryAttributes = function (pObjectType, pConsumer, pFiltered)
{
var mandatoryAttributesSelect = newSelect("AB_ATTRIBUTE_ID, MIN_COUNT")
.from("AB_ATTRIBUTEUSAGE")
.join("AB_ATTRIBUTE", "AB_ATTRIBUTE_ID = AB_ATTRIBUTEID")
.where("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
.and("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.COMBOVALUE(), SqlBuilder.NOT_EQUAL())
.and("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.GROUP(), SqlBuilder.NOT_EQUAL())
.and("ATTRIBUTE_ACTIVE = 1")
.and("MIN_COUNT > 0");
if (pFiltered)
{
var possibleIds = AttributeUtil.getPossibleAttributes(pObjectType, false, pFiltered);
if (possibleIds.length > 0)
mandatoryAttributesSelect.and("AB_ATTRIBUTE.AB_ATTRIBUTEID", possibleIds, SqlBuilder.IN())
else
return;
}
var mandatoryAttributes = mandatoryAttributesSelect.table();
mandatoryAttributes.forEach(function (usage)
{
//adding an attribute more than 20 times would be too much (having a min_count > 20 is very unlikely)
for (let i = 0; i < usage[1] && i < 20; i++)
neon.addRecord(pConsumer, {
"AB_ATTRIBUTE_ID" : usage[0]
});
});
}
/**
* Clears rows of attribute.
*
* @param {String} pConsumer <p>
* The name of the attribute relation consumer.<br>
*/
AttributeRelationUtils.clearAttributes = function (pConsumer)
{
var insertedLinks = vars.get("$field." + pConsumer + ".insertedRows");
var updatedLinks = vars.get("$field." + pConsumer + ".changedRows");
insertedLinks.concat(updatedLinks).forEach(function (link)
{
if (link["AB_ATTRIBUTE_ID"])
neon.deleteRecord(pConsumer, link["#UID"]);
});
}
/**
* Checks if the count of the used attributes <br>
* is valid and returns a message if it's not.<br>
*
* @param {String} pRowId <p>
* The row id of the entity.<br>
* @param {String} pObjectType=null <p>
* The object type.<br>
* @param {String} pConsumerField <p>
* The name of the attribute relation consumer.<br>
* @param {String} pFilteredAttributeIds <p>
* Filters the attributes that are, validated<br>
* this should be the same as the<br>
* FilteredAttributeIds_param.<br>
* @return {String} <p>
* The validation message or an empty<br>
* string if everything is ok.<br>
*/
AttributeRelationUtils.validateAttributeCount = function (pRowId, pObjectType, pConsumerField, pFilteredAttributeIds)
{
var attributeChanges = {};
var deletedRows = vars.get("$field." + pConsumerField + ".deletedRows");
var changedRows = vars.get("$field." + pConsumerField + ".changedRows");
var insertedRows = vars.get("$field." + pConsumerField + ".insertedRows");
if (deletedRows)
{
deletedRows.forEach(function (row)
{
this[row.UID] = "";
}, attributeChanges);
}
if (changedRows)
{
changedRows.forEach(function (row)
{
this[row.UID] = row.AB_ATTRIBUTE_ID;
}, attributeChanges);
}
//get the current count of usages considering the changes
//this will merge the counts of attributeChanges and the already stored attributerelations
var countObj = AttributeRelationUtils.countAttributeRelations(pRowId, pObjectType, attributeChanges);
if (insertedRows) //append the new rows
{
insertedRows.forEach(function (row)
{
this[row.AB_ATTRIBUTE_ID] = (this[row.AB_ATTRIBUTE_ID] || 0) + 1;
}, countObj);
}
if (changedRows) //append the new rows (if they are added by default but not filled with a value)
{
changedRows.forEach(function (row)
{
if(!row.DATE_NEW && !row.USER_NEW)
{
this[row.AB_ATTRIBUTE_ID] = (this[row.AB_ATTRIBUTE_ID] || 0) + 1;
}
}, countObj);
}
var possibleAttributes = AttributeUtil.getPossibleAttributes(pObjectType, undefined, pFilteredAttributeIds);
var minMaxCounts = [];
if (possibleAttributes.length > 0)
{
var minMaxCountsSelect = newSelect("AB_ATTRIBUTEID, ATTRIBUTE_NAME, MIN_COUNT, MAX_COUNT")
.from("AB_ATTRIBUTEUSAGE")
.join("AB_ATTRIBUTE", "AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID")
.where("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", possibleAttributes, SqlBuilder.IN())
.and("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
//retrieve all min/max counts of the possible attributes
minMaxCounts = minMaxCountsSelect.table();
}
var validationMessage = [];
minMaxCounts.forEach(function ([attributeId, name, minCount, maxCount])
{
let count = this[attributeId] || 0;
//compares the actual usage with the min and max count and generates a message if the usage is too low or too high
if (count < minCount)
validationMessage.push(translate.withArguments("Attribute \"%0\" has to be used at least %1.", [name, _getTranslatedCount(minCount)]));
if (maxCount && count > maxCount)
validationMessage.push(translate.withArguments("Attribute \"%0\" can't be used more than %1.", [name, _getTranslatedCount(maxCount)]));
}, countObj);
return validationMessage.join("\n");
//returns the correct count expression by choosing either singular (1 time) or plural (2 times)
function _getTranslatedCount (pCount)
{
if (pCount == 1)
return pCount + " " + translate.text("${COUNT_PREPOSITION_SINGLE}");
return pCount + " " + translate.text("${COUNT_PREPOSITION_MULTIPLE}");
}
}
/**
* Counts attribute relations.
*
* @param {String} pRowId <p>
* The row id of the entity.<br>
* @param {String} pObjectType=null <p>
* The object type.<br>
* @param {Object} pAttributeChanges=null <p>
* Object containing changes and deletions <br>
* of attributes structure = {attributeRelationId <br>
* : new attributeId or "" when deleted}<br>
* @return {Object} <p>
* Object with attribute ids and the count of <br>
* the usage (without new rows).<br>
*/
AttributeRelationUtils.countAttributeRelations = function (pRowId, pObjectType, pAttributeChanges)
{
//use cases:
//complete new row ==> increase count by 1 for that attribute [this is done in another function]
//row removed ==> decrease count by 1
//row edit: replace one attribute by another attribute ==> increase the new attribute count and decrease the old attribute count
//row edit: replace attribute with no new value ==> decrease count for the old attribute
//unchanged (already stored) row ==> increase count
var countObj = {};
storedAttributeRelations = new AttributeRelationQuery()
.objectRowId(pRowId)
.objectType(pObjectType)
.getAttributes();
storedAttributeRelations.forEach(function (storedRow) {
var storedAttributeId = storedRow.attributeId;
var storedAttrRelationId = storedRow.attributeRelationId;
var currentAttributeId = storedAttributeId;
//merging the data that is stored in the DB and the provided changes
if (pAttributeChanges && storedAttrRelationId in pAttributeChanges)
currentAttributeId = pAttributeChanges[storedAttrRelationId];
// it doesn't matter if a row has been deleted or if the attribute has been set to "nothing"
if (currentAttributeId == "")
_decrCount(storedAttributeId);
else
{
_incrCount(currentAttributeId);
if (currentAttributeId != storedAttributeId)
_decrCount(storedAttributeId);
}
});
function _incrCount(pAttributeId)
{
if (countObj[pAttributeId])
countObj[pAttributeId]++;
else
countObj[pAttributeId] = 1;
}
function _decrCount(pAttributeId)
{
if (countObj[pAttributeId])
countObj[pAttributeId]--;
else
countObj[pAttributeId] = 0;
}
return countObj;
}
/*********************************************************************************************************************/
/**
* Object for the enumeration and management of all attribute types.
* This Object is only for the general definition of attribute types and for getting
* data about every type, anything that has to do with a specific attribute (= the function requires an attribute id)
* should be done in AttributeUtils.
*
* Every AttributeType also needs a keyword entry for the keyword container 'AttributeType'
*/
function AttributeTypes () {}
AttributeTypes.TEXT = function () {return "TEXT";}
AttributeTypes.DATE = function () {return "DATE";}
AttributeTypes.NUMBER = function () {return "NUMBER";}
AttributeTypes.BOOLEAN = function () {return "BOOLEAN";}
AttributeTypes.COMBO = function () {return "COMBO";}
AttributeTypes.COMBOVALUE = function () {return "COMBOVALUE";}
AttributeTypes.GROUP = function () {return "GROUP";}
AttributeTypes.KEYWORD = function () {return "KEYWORD";}
AttributeTypes.VOID = function () {return "VOID";}
AttributeTypes.MEMO = function () {return "MEMO";}
AttributeTypes.OBJECTSELECTION = function () {return "OBJECTSELECTION";}
AttributeTypes.THEME = function () {return "THEME";}
AttributeTypes.INTEGER = function () {return "INTEGER";}
/**
* Object for representing a single AttributeType. Note: The entries in AttributeTypes are not actually of this type, so "instanceof" will not
* work for them. The default properties of an attribute type are still initialized by calling this constructor, to make at least code
* completion possible.
*/
function AttributeType ()
{
/**
* Returns a String representation of the AttributeType
*
* @return {String} String that matches the keyId of the AttributeType Keyword
*/
this.toString = function () {return this();};
/**
* ContentType of the value field
*/
this.contentType = null;
/**
* Database field that should hold the value
*/
this.databaseField = null;
/**
* If set to true, max usage count is always 1 (optional)
*/
this.singleSelection = false;
/**
* Array of attribute types that can have this attribute as parent (optional)
*/
this.possibleChildren = null;
/**
* Name of the dropdowndefinition field (optional)
*/
this.dropDownDefinitionTitle = "";
/**
* Use the lookup field
*/
this.useLookup = false;
/**
* Function that returns an sub-sql to resolve the attribute display value (optional)
*
* @param {Object} pAttributeData Object with the attribute properties
* @return {String} sql expression that resolves the display value
*/
this.getDisplayValueSql = function (pAttributeData)
{
return this.databaseField;
}
/**
* Function to resolve the display value (optional)
*
* @param {String} pValue raw value
* @return {String} the display value
*/
this.getViewValue = function (pValue)
{
return pValue;
}
/**
* Function to load all possible values for "dropdowndefinition" (optional)
*
* @return {Array|null} array that can be used in the dropDownProcess (if defined)
*/
this.getDropDownDefinitions = function ()
{
return null;
}
/**
* Function to define validation parameters that can be set for an attribute (optional)
*
* @return {Object[]} dynamicForm definition for the parameter fields
*/
this.getValidationParameters = function () {},
/**
* Function to validate the entered attribute value (optional)
*
* @param {String} pValue the value to be validated
* @param {Object} pValidationParams validation parameters defined for the attribute
* @return {String|Boolean} A validation message string if the validation failed, true if the value is valid
*/
this.validateValue = function (pValue, pValidationParams)
{
return true;
}
}
{ //block for encapsulation
for (let typeName in AttributeTypes)
{
AttributeType.call(AttributeTypes[typeName]);
}
}
//the "get" function is defined like this so it does not show up in for..in loops (enumerable: false)
Object.defineProperty(AttributeTypes, "get", {
enumerable: false,
writable: true
});
/**
* Get the AttributeType object with the given name.
*
* @param {String} pAttributeTypeName name of the attribute type
* @return {AttributeType} the attribute type, or null if the given name was invalid
*/
AttributeTypes.get = function (pAttributeTypeName)
{
if (!pAttributeTypeName)
return null;
return AttributeTypes[pAttributeTypeName.toString().trim()] || null;
}
/*** In the following section, custom properties are defined for every AttributeType ***/
Object.assign(AttributeTypes.TEXT, {
contentType: "TEXT",
databaseField: "CHAR_VALUE",
getValidationParameters: function ()
{
return [{
id: "regExp",
name: translate.text("Regular expression"),
contentType: "TEXT",
isReadable: true,
isWritable: true,
isRequired: false,
value: null
}];
},
validateValue: function (pValue, pValidationParams)
{
if (pValidationParams && pValidationParams.regExp)
{
var regExParts = pValidationParams.regExp.match(new RegExp('^/(.*?)/([gimy]*)$'));
var regEx;
if (regExParts)
regEx = new RegExp(regExParts[1], regExParts[2]);
else
regEx = new RegExp(pValidationParams.regExp);
if (!regEx.test(pValue))
return translate.text("Invalid value");
}
return true;
}
});
Object.assign(AttributeTypes.DATE, {
contentType: "DATE",
databaseField: "DATE_VALUE",
getViewValue: function (pValue)
{
return datetime.toDate(pValue, translate.text("dd.MM.yyyy"));
}
});
Object.assign(AttributeTypes.NUMBER, {
contentType: "NUMBER",
databaseField: "NUMBER_VALUE",
getValidationParameters: function ()
{
return [{
id: "minValue",
name: translate.text("Minimum"),
contentType: "NUMBER",
isReadable: true,
isWritable: true,
isRequired: false,
value: null
},{
id: "maxValue",
name: translate.text("Maximum"),
contentType: "NUMBER",
isReadable: true,
isWritable: true,
isRequired: false,
value: null
}];
},
validateValue: function (pValue, pValidationParams)
{
if (pValidationParams)
{
pValue = Number(pValue);
if (!Utils.isNullOrEmptyString(pValidationParams.minValue) && pValue < pValidationParams.minValue)
return translate.withArguments("Value is too small, the minimum is %0", [pValidationParams.minValue]);
if (!Utils.isNullOrEmptyString(pValidationParams.maxValue) && pValue > pValidationParams.maxValue)
return translate.withArguments("Value is too big, the maximum is %0", [pValidationParams.maxValue]);
}
return true;
}
});
Object.assign(AttributeTypes.BOOLEAN, {
contentType: "BOOLEAN",
databaseField: "INT_VALUE",
singleSelection: true,
getDisplayValueSql: function (pAttributeData)
{
var valueField = "AB_ATTRIBUTERELATION." + this.databaseField;
return SqlBuilder.caseWhen(valueField, "1").thenString(translate.text("Yes"))
.when(valueField, "0").thenString(translate.text("No"));
},
getViewValue: function (pValue)
{
return Utils.toBoolean(pValue) ? translate.text("Yes") : translate.text("No");
}
});
Object.assign(AttributeTypes.COMBO, {
contentType: "UNKNOWN",
databaseField: "ID_VALUE",
possibleChildren: [AttributeTypes.COMBOVALUE()],
//in most cases the view value of this attribute type is loaded via a direct sql join for less queries and better performance
getDisplayValueSql: function (pAttributeData)
{
var valueField = "AB_ATTRIBUTERELATION." + this.databaseField;
var values = newSelect(["AB_ATTRIBUTEID", "ATTRIBUTE_NAME"])
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeData.attributeId)
.and("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.COMBOVALUE())
.table();
var sql = SqlBuilder.caseStatement(values, valueField);
values.forEach(function ([key, value])
{
sql.when(valueField, key).thenString(translate.text(value));
});
return sql.toString();
},
getViewValue: function (pValue)
{
var viewValue = newSelect("AB_ATTRIBUTE.ATTRIBUTE_NAME")
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.AB_ATTRIBUTEID", pValue)
.and("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.COMBOVALUE())
.cell();
return viewValue ? translate.text(viewValue) : viewValue;
}
});
Object.assign(AttributeTypes.GROUP, {
possibleChildren: [
AttributeTypes.GROUP(),
AttributeTypes.TEXT(),
AttributeTypes.NUMBER(),
AttributeTypes.COMBO(),
AttributeTypes.VOID(),
AttributeTypes.THEME(),
AttributeTypes.KEYWORD(),
AttributeTypes.OBJECTSELECTION(),
AttributeTypes.MEMO(),
AttributeTypes.DATE(),
AttributeTypes.BOOLEAN()
]
});
Object.assign(AttributeTypes.KEYWORD, {
contentType: "UNKNOWN",
databaseField: "ID_VALUE",
getDisplayValueSql: function (pAttributeData)
{
var valueField = "AB_ATTRIBUTERELATION." + this.databaseField;
return KeywordUtils.getResolvedTitleSqlPart(pAttributeData.dropDownDefinition, valueField);
},
getViewValue: function (pValue, pKeyword)
{
return KeywordUtils.getViewValue(pKeyword, pValue);
},
dropDownDefinitionTitle: "Keyword",
getDropDownDefinitions: function ()
{
return KeywordUtils.getContainerNames().map(function (e)
{
return [e, e];//currently the first column is ID, second view value - which is the same because there is no ID for keyword-containers
});
}
});
Object.assign(AttributeTypes.VOID, {
possibleChildren: [AttributeTypes.VOID()],
singleSelection: true
});
Object.assign(AttributeTypes.MEMO, {
contentType: "LONG_TEXT",
databaseField: "CHAR_VALUE"
});
Object.assign(AttributeTypes.OBJECTSELECTION, {
contentType: "UNKNOWN",
databaseField: "ID_VALUE",
useLookup: true,
getViewValue: function (pValue, pModule)
{
if (pValue)
{
if (pModule == "Employee_entity")
pValue = EmployeeUtils.prefixUserId(pValue);
var title = "#CONTENTTITLE";
var config = entities.createConfigForLoadingRows()
.entity(pModule)
.fields([title])
.uid(pValue);
// first check count to avoid errors at getRow (make sure entry exists)
var count = entities.getRowCount(config);
if (count && count > 0) {
var rows = entities.getRow(config);
pValue = rows ? rows[title] : pValue;
} else {
// return null if entry does not exist
// -> react accordingly when calling this function
pValue = null;
}
}
return pValue;
},
dropDownDefinitionTitle: "Module",
getDropDownDefinitions: function ()
{
// TODO: use loadEntity from context_entity
var dropDownList = [];
project.getDataModels(project.DATAMODEL_KIND_ENTITY).forEach(
function (entity)
{
if (entity[1] && AttributeTypes.OBJECTSELECTION._selectableEntities.has(entity[0]))
dropDownList.push([entity[0], translate.text(entity[1])]);
}
);
return dropDownList;
},
/** @private */
_selectableEntities: new Set([
"ObjectRelationType_entity",
"DocumentTemplate_entity",
"SupportTicket_entity",
"Organisation_entity",
"Salesproject_entity",
"Productprice_entity",
"SerialLetter_entity",
"AnyContact_entity",
"Salutation_entity",
"Attribute_entity",
"Activity_entity",
"Contract_entity",
"Campaign_entity",
"BulkMail_entity",
"Employee_entity",
"Language_entity",
"Product_entity",
"Person_entity",
"Offer_entity",
"Order_entity",
"Task_entity",
"Role_entity"
])
});
Object.assign(AttributeTypes.THEME, {
contentType: "LONG_TEXT",
databaseField: "CHAR_VALUE",
possibleChildren: [AttributeTypes.THEME()]
});
Object.assign(AttributeTypes.INTEGER, {
contentType: "NUMBER",
databaseField: "INT_VALUE",
getValidationParameters: function ()
{
return [{
id: "minValue",
name: translate.text("Minimum"),
contentType: "NUMBER",
isReadable: true,
isWritable: true,
isRequired: false,
value: null
},{
id: "maxValue",
name: translate.text("Maximum"),
contentType: "NUMBER",
isReadable: true,
isWritable: true,
isRequired: false,
value: null
}];
},
validateValue: function (pValue, pValidationParams)
{
if (!Utils.isInteger(pValue))
return translate.text("Value must be an integer");
if (pValidationParams)
{
pValue = Number(pValue);
if (!Utils.isNullOrEmptyString(pValidationParams.minValue) && pValue < pValidationParams.minValue)
return translate.withArguments("Value is too small, the minimum is %0", [pValidationParams.minValue]);
if (!Utils.isNullOrEmptyString(pValidationParams.maxValue) && pValue > pValidationParams.maxValue)
return translate.withArguments("Value is too big, the maximum is %0", [pValidationParams.maxValue]);
}
return true;
}
})
//reference for compatibility with old name
var $AttributeTypes = AttributeTypes;
function AttributeTypeUtil () {}
/**
* Returns the required contentType for the given attribute type.
*
* @param {String} pAttributeType <p>
* The attribute type (use the values of the AttributeTypes<br>
* object, e. g. AttributeTypes.TEXT)<br>
* @return {String} <p>
* The contentType for the attribute.<br>
*/
AttributeTypeUtil.getContentType = function (pAttributeType)
{
var type = AttributeTypes.get(pAttributeType);
return type ? type.contentType : null;
}
/**
* Returns if the type is a group type.
*
* @param {String} pAttributeType <p>
* The attribute type (use the values of the <br>
* AttributeTypes object, e. g. AttributeTypes.TEXT)
* @return {Boolean} <p>
* If the type is a group type it returns true.
*/
AttributeTypeUtil.isGroupType = function (pAttributeType)
{
var type = AttributeTypes.get(pAttributeType);
return type && !Utils.isNullOrEmpty(type.possibleChildren);
}
/**
* Returns the database field for the given<br>
* attribute type that holds the value of the<br>
* attribute.<br>
*
* @param {String} pAttributeType <p>
* The attribute type (use the values of<br>
* the AttributeTypes object, e.g.<br>
* AttributeTypes.TEXT)<br>
* @return {String} <p>
* The database field for the attribute.<br>
*/
AttributeTypeUtil.getDatabaseField = function (pAttributeType)
{
var type = AttributeTypes.get(pAttributeType);
return type ? type.databaseField : null;
}
/**
* Returns the possible children types for the given attribute type.<br>
*
* @param {String} pAttributeType <p>
* The attribute type (use the values <br>
* of the AttributeTypes object, e. g.<br>
* AttributeTypes.TEXT)<br>
* @return {String[]|null} <p>
* The possible children types, can be null.<br>
*/
AttributeTypeUtil.getPossibleChildren = function (pAttributeType)
{
var type = AttributeTypes.get(pAttributeType);
return type ? type.possibleChildren : null;
}
/**
* Checks whether the given attribute type is<br>
* is a single selection attribute type.<br>
*
* @param {String} pAttributeType <p>
* The attribute type (use the values<br>
* of the AttributeTypes object, e. g.<br>
* AttributeTypes.TEXT)<br>
* @return {Boolean} <p>
* if the attribute can only be used once<br>
*/
AttributeTypeUtil.isSingleSelection = function (pAttributeType)
{
var type = AttributeTypes.get(pAttributeType);
return type ? type.singleSelection : null;
}
/**
* Returns the title of the "dropDownDefinition"
*
* @param {String} pAttributeType <p>
* The attribute type (use the values<br>
* of the AttributeTypes object, e. g.<br>
* AttributeTypes.TEXT)<br>
* @return {String} <p>
* .<br>
*/
AttributeTypeUtil.getDropDownDefinitionTitle = function (pAttributeType)
{
var type = AttributeTypes.get(pAttributeType);
return type ? type.dropDownDefinitionTitle : "";
}
/**
* Returns a function to resolve the displayValue depending on the attribute type.
*
* @param {String} pAttributeType <p>
* The attribute type (use the values<br>
* of the AttributeTypes object, e. g.<br>
* AttributeTypes.TEXT)<br>
* @return {Function} <p>
* A function that resolves the displayValue or null if the type is invalid<br>
*/
AttributeTypeUtil.getDisplayValueSqlFn = function (pAttributeType)
{
var attributeType = AttributeTypes.get(pAttributeType);
if (!attributeType)
return null;
return attributeType.getDisplayValueSql.bind(attributeType);
}
/**
* Compare the given pAttributeType with the attribute type string
* "OBJECTSELECTION".
*
* @param {String} pAttributeType <p>
* The attribute type which shall be comapred.
* @return {Boolean} <p>
* Returns true, if the given attribute type is equal<br>
* with the attribute string "OBJECTSELECTION" and <br>
* false, if not.<br>
*/
AttributeTypeUtil.useLookup = function (pAttributeType)
{
var type = AttributeTypes.get(pAttributeType);
return type ? type.useLookup : false;
}
/**
* Compare the given attribute type with every other<br>
* existing attribute type and returns every compared <br>
* type which is a possible parent type.<br>
*
* @param {String} pChildType <p>
* The attribute type which shall be used to compare.
* @return {Array} <p>
* Returns all possible parent attribute types.<br>
*/
AttributeTypeUtil.getGroupTypes = function (pChildType)
{
var groupTypes = [];
for (let type in AttributeTypes)
{
if (AttributeTypeUtil.isGroupType(type) && (!pChildType || (!AttributeTypeUtil.getPossibleChildren(type) || AttributeTypeUtil.getPossibleChildren(type).indexOf(pChildType) !== -1)) )
groupTypes.push(type);
}
return groupTypes;
}
/**
* If the given attribute type is a <br>
* valid type and it has a getViewValue<br>
* function it will return the matching<br>
* view value to the given pValue.<br>
*
* @param {String} pAttributeType <p>
* The attribute type.<br>
* @param {String} pValue <p>
* The value.<br>
* @param {String} pKeyword <p>
* The keyword.<br>
* @return {String} <p>
* Returns the view value.
*/
AttributeTypeUtil.getAttributeViewValue = function (pAttributeType, pValue, pKeyword)
{
var type = AttributeTypes.get(pAttributeType);
return type ? type.getViewValue(pValue, pKeyword) : pValue;
}
/**
* Initializes the type columns.
*/
AttributeTypeUtil._initTypeColumnData = function ()
{
var columns = [];
var typeColumnMap = {};
for (let type in AttributeTypes)
{
type = AttributeTypes[type];
if (type.databaseField)
{
var typeKey = type.toString();
var colIndex = columns.indexOf(type.databaseField);
if (colIndex == -1)
{
colIndex = columns.length;
columns.push(type.databaseField);
}
typeColumnMap[typeKey] = colIndex;
}
}
this._allDBColumns = columns;
this._typeColumnMap = typeColumnMap;
}
/**
* Return the all database fields/columns of the every attribute type.
*
* @return {String} <p>
* All database fields/columns.
*/
AttributeTypeUtil.getAllDatabaseFields = function ()
{
if (this._allDBColumns == undefined)
AttributeTypeUtil._initTypeColumnData();
return this._allDBColumns;
}
/**
* Returns the type column index.
*
* @param {String} pAttributeType <p>
* The attribute type of you want the column<br>
* type index back.<br>
* @return {String}
*/
AttributeTypeUtil.getTypeColumnIndex = function (pAttributeType)
{
if (this._typeColumnMap == undefined)
AttributeTypeUtil._initTypeColumnData();
return this._typeColumnMap[pAttributeType.trim()];
}
/*********************************************************************************************************************/
/**
* Functions for AttributeUsages.<br>
* <b><i>Do not instanciate this!</i></b>
*/
function AttributeUsageUtil () {}
/**
* Creates AttributeUsages for all subordinate attributes <br>
* of an attribute.This is required when an usage is added <br>
* to a superordinate attribute.<br>
*
* @param {String} pAttributeId <p>
* The id of the superordinate attribute.<br>
* @param {String} pObjectType <p>
* The context.<br>
*/
AttributeUsageUtil.insertChildrenUsages = function (pAttributeId, pObjectType)
{
if (!pAttributeId)
return;
var table = "AB_ATTRIBUTEUSAGE";
var columns = ["AB_ATTRIBUTEUSAGEID", "AB_ATTRIBUTE_ID", "OBJECT_TYPE", "MAX_COUNT"];
var inserts = [];
_addInserts(pAttributeId, pObjectType);
db.inserts(inserts);
function _addInserts (pAttributeId, pObjectType)
{
var attributes = newSelect(["AB_ATTRIBUTEID", "ATTRIBUTE_TYPE",
newSelect("count(*)")
.from("AB_ATTRIBUTEUSAGE")
.where("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID")
.and("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)])
.from("AB_ATTRIBUTE")
.where("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.COMBOVALUE(), SqlBuilder.NOT_EQUAL())
.and("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId)
.table();
attributes.forEach(function (row)
{
if (row[2] == "0")
{
let maxCount = AttributeTypeUtil.isSingleSelection(row[1])
? "1"
: "";
let values = [util.getNewUUID(), row[0], pObjectType, maxCount];
inserts.push([table, columns, null, values]);
}
_addInserts(row[0], pObjectType);
});
}
}
/**
* Updates AttributeUsages for all subordinate attributes <br>
* of an attribute. This is required when an usage of a <br>
* superordinate attribute is changed.<br>
*
* @param {String} pAttributeId <p>
* The id of the superordinate attribute.<br>
* @param {String} pOldObjectType <p>
* The old context.<br>
* @param {String} pNewObjectType <p>
* The new context.<br>
*/
AttributeUsageUtil.updateChildrenUsages = function (pAttributeId, pOldObjectType, pNewObjectType)
{
if (!pNewObjectType || !pAttributeId || !pOldObjectType)
return;
var table = "AB_ATTRIBUTEUSAGE";
var countSubQuery = newSelect("count(*)")
.from("AB_ATTRIBUTEUSAGE")
.where("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pNewObjectType)
.and("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID");
var sqlSelect = newSelect(["AB_ATTRIBUTEID", "AB_ATTRIBUTEUSAGEID", countSubQuery])
.from("AB_ATTRIBUTE")
.leftJoin("AB_ATTRIBUTEUSAGE", newWhere()
.and("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pOldObjectType)
.and("AB_ATTRIBUTEID = AB_ATTRIBUTE_ID"));
var updateCond = newWhere();
//it is possible that the new objectType is already in a subordinate attribute
//and an update could cause a duplicate entry so one has to be deleted
var deleteCond = newWhere();
_addUpdateIds(pAttributeId, pOldObjectType);
updateCond.updateData(true, table, ["OBJECT_TYPE"], null, [pNewObjectType]);
deleteCond.deleteData(true, table);
function _addUpdateIds (pAttributeId)
{
sqlSelect.clearWhere()
.where("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.COMBOVALUE(), SqlBuilder.NOT_EQUAL())
.and("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
var attributes = sqlSelect.table();
attributes.forEach(function (row)
{
if (row[1] && row[2] != "0")
deleteCond.or("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[1]);
else if (row[1])
updateCond.or("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[1]);
_addUpdateIds(row[0]);
});
}
}
/**
* Deletes AttributeUsages for all subordinate attributes of an attribute.
* This is required when an usage is removed from a superordinate attribute.
*
* @param {String} pAttributeId <p>
* The id of the superordinate attribute.<br>
* @param {String} pObjectType <p>
* The context.
*/
AttributeUsageUtil.deleteChildrenUsages = function (pAttributeId, pObjectType)
{
var attributeSelect = newSelect("AB_ATTRIBUTEID, AB_ATTRIBUTEUSAGEID")
.from("AB_ATTRIBUTE")
.leftJoin("AB_ATTRIBUTEUSAGE", newWhere("AB_ATTRIBUTEID = AB_ATTRIBUTE_ID")
.and("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType));
var deleteCond = newWhere().from("AB_ATTRIBUTEUSAGE");
_addDeleteIds(pAttributeId, pObjectType);
deleteCond.deleteData();
function _addDeleteIds (pAttributeId)
{
attributeSelect.clearWhere()
.where("AB_ATTRIBUTE.ATTRIBUTE_TYPE", AttributeTypes.COMBOVALUE(), SqlBuilder.NOT_EQUAL())
.and("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
var attributes = attributeSelect.table();
attributes.forEach(function (row)
{
if (row[1])
deleteCond.or("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[1])
_addDeleteIds(row[0]);
});
}
}
/**
* Deletes duplicate attribute usages.
*
* @param {String} [pAttributeId=null] <p>
* Attribute id, if omitted, all duplicates will be deleted.<br>
*/
AttributeUsageUtil.removeDuplicates = function (pAttributeId)
{
var attributeSelect = newSelect("AB_ATTRIBUTEUSAGEID, AB_ATTRIBUTE_ID, OBJECT_TYPE")
.from("AB_ATTRIBUTEUSAGE")
.where(null, newSelect("AB_ATTRIBUTEUSAGEID")
.from("AB_ATTRIBUTEUSAGE", "USAGEDUP")
.where("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = USAGEDUP.AB_ATTRIBUTE_ID")
.and("AB_ATTRIBUTEUSAGE.OBJECT_TYPE = USAGEDUP.OBJECT_TYPE")
.and("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID != USAGEDUP.AB_ATTRIBUTEUSAGEID"),
SqlBuilder.EXISTS());
attributeSelect.andIfSet("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", pAttributeId);
var duplicates = attributeSelect.table();
var usageObj = {};
var deleteCond = newWhere().from("AB_ATTRIBUTEUSAGE");
duplicates.forEach(function (row)
{
if (!(row[1] in this))
this[row[1]] = {};
if (row[2] in this[row[1]])
deleteCond.or("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[0]);
this[row[1]][row[2]] = true;
}, usageObj);
deleteCond.deleteData();
}
/*************************************************************************************************/
/**
*
* An AttributeRelationQuery can be used for getting the
* value and other properties of an AttributeRelation. <br>
* <i><u>You have to instanciate it with "new".</u></i>
* <p>
* This is built like this because there are several different scenarios for
* loading the values or other properties of one or more attribute relations. Because of this,
* the constructor takes in the common parameters for loading attribute relations and you can
* use methods of the constructed object to configure the query and get the desired result.
*
* @param {String} pObjectRowId <p>
* The object row id. (e.g.: contact id)<br>
* @param {String} pAttributeId <p>
* The attribute id.<br>
* @param {String} pObjectType <p>
* The object type. (e.g.: "Organisation")
* @class
*/
function AttributeRelationQuery (pObjectRowId, pAttributeId, pObjectType)
{
this._rowId = pObjectRowId || null;
this._objectType = pObjectType || null;
this._attributeIds = pAttributeId ? [pAttributeId] : null;
this._attributeTypes = null;
this._includeFullAttributeName = false;
this._includeDisplayValue = false;
}
/**
* Sets the object row id for the query.
*
* @param {String} pObjectRowId <p>
* The row id of the object. (e.g.: contact id)<br>
* @return {Object} <p>
* Returns AttributeRelationQuery object<br>
* with the object row id set.<br>
*/
AttributeRelationQuery.prototype.objectRowId = function (pObjectRowId)
{
this._rowId = pObjectRowId;
return this;
}
/**
* Sets the object-type for the query.<br>
*
* @param {String} pObjectType <p>
* The object type. (e.g.: "Organisation")<br>
* @return {Object} <p>
* Returns AttributeRelationQuery object<br>
* with the object type set.<br>
*/
AttributeRelationQuery.prototype.objectType = function (pObjectType)
{
this._objectType = pObjectType;
return this;
}
/**
* Sets only one attribute id for the query.<br>
*
* @param {Array} pAttributeId <p>
* The attribute id.<br>
* @return {Object} <p>
* Returns AttributeRelationQuery object<br>
* with the attribute id set.<br>
*/
AttributeRelationQuery.prototype.attributeId = function (pAttributeId)
{
this._attributeIds = [pAttributeId];
return this;
}
/**
* Sets the attribute ids for the query.
*
* @param {Array} pAttributeIds <p>
* The attribute ids in a array.<br>
* @return {Object} <p>
* Returns AttributeRelationQuery object<br>
* with the attribute ids set.<br>
*/
AttributeRelationQuery.prototype.attributeIds = function (pAttributeIds)
{
this._attributeIds = pAttributeIds;
return this;
}
/**
* Sets the attribute type for the query.
*
* @param {Array} pAttributeTypes <p>
* The attribute types.<br>
* @return {Object} <p>
* Returns the AttributeRelationQuery object<br>
* with the attributeTypes set.<br>
*/
AttributeRelationQuery.prototype.attributeTypes = function (pAttributeTypes)
{
this._attributeTypes = pAttributeTypes;
return this;
}
/**
* If this method was called, the query result will contain the fullAttributeName.
*
* @return {Object} <p>
* Return the AttributeRelationQuery object<br>
* with the option includeFullAttributeName enabled.<br>
*/
AttributeRelationQuery.prototype.includeFullAttributeName = function ()
{
this._includeFullAttributeName = true;
return this;
}
/**
* If this method was called, the query result will contain the displayValue.
*
* @return {Object} <p>
* Return the AttributeRelationQuery object<br>
* with the option includeDisplayValue enabled.<br>
*/
AttributeRelationQuery.prototype.includeDisplayValue = function ()
{
this._includeDisplayValue = true;
return this;
}
/**
* Executes the query and returns the result, depending on the properties of the AttributeRelationQuery object.
*
* @return {AttributeRelation[]} <p>
* Array of objects. By default, the objects contain the properties:
* <ul>
* <li>attributeId</li>
* <li>value</li>
* <li>attributeRelationId</li>
* <li>attributeName</li>
* <li>attributeType</li>
* </ul>
* If includeDisplayValue is true, the object also contains<br>
* the property 'displayValue' and if includeFullAttributeName <br>
* is true, there is also the property 'fullAttributeName'.<br>
*/
AttributeRelationQuery.prototype.getAttributes = function ()
{
var defaultFields = [
"AB_ATTRIBUTE.ATTRIBUTE_TYPE",
"AB_ATTRIBUTE.DROPDOWNDEFINITION",
"AB_ATTRIBUTE.ATTRIBUTE_NAME",
"COMBOVAL.ATTRIBUTE_NAME",
"AB_ATTRIBUTE.AB_ATTRIBUTEID",
"AB_ATTRIBUTERELATION.AB_ATTRIBUTERELATIONID",
"AB_ATTRIBUTERELATION.OBJECT_ROWID",
"AB_ATTRIBUTERELATION.OBJECT_TYPE"
];
var valueFields = AttributeTypeUtil.getAllDatabaseFields();
var attributeValues = AttributeRelationUtils.getAttributeSqlBuilder(defaultFields.concat(valueFields), this._rowId, this._objectType)
.andIfSet("AB_ATTRIBUTERELATION.AB_ATTRIBUTE_ID", this._attributeIds, SqlBuilder.IN())
.andIfSet("AB_ATTRIBUTE.ATTRIBUTE_TYPE", this._attributeTypes, SqlBuilder.IN())
.table();
if (attributeValues.length == 0)
return [];
var mappingFn = function (row)
{
var attrObj = new AttributeRelation(row[5], row[4], row[AttributeTypeUtil.getTypeColumnIndex(row[0]) + defaultFields.length],
row[2], row[0], row[6], row[7]);
if (this._includeDisplayValue)
{
if (row[0].trim() == AttributeTypes.COMBO())
attrObj.displayValue = translate.text(row[3]);
else
attrObj.displayValue = AttributeTypeUtil.getAttributeViewValue(row[0].trim(), attrObj.value, row[1]);
}
if (this._includeFullAttributeName)
{
attrObj.fullAttributeName = AttributeUtil.getFullAttributeName(row[4]);
}
return attrObj;
}
return attributeValues.map(mappingFn, this);
}
/**
* If this method is executed on your AttributeRelationQuery<br>
* object it will return only one attribute.
*
* @return {AttributeRelation} <p>
* Returns the AttributeRelationQuery object<br>
* with only a sinlge attribute.<br>
*/
AttributeRelationQuery.prototype.getSingleAttribute = function ()
{
if (!this._attributeIds || this._attributeIds.length !== 1)
throw new Error("You have to specify a single attribute id");
return this.getAttributes()[0] || null;
}
/**
* Executes the query and returns a single value.<br>
* For this, there must be a attribute id set.
*
* @return {String} <p>
* The single value.
*/
AttributeRelationQuery.prototype.getSingleAttributeValue = function ()
{
var attribute = this.getSingleAttribute();
return attribute ? attribute.value : null;
}
/**
* Executes the query and returns the count of datasets.
*
* @return {Number} <p>
* The number of attribute relations.
*/
AttributeRelationQuery.prototype.getAttributeCount = function ()
{
return parseInt(AttributeRelationUtils.getAttributeSqlBuilder("count(*)", this._rowId, this._objectType)
.and(newWhereIfSet( "AB_ATTRIBUTERELATION.AB_ATTRIBUTE_ID", this._attributeIds, SqlBuilder.IN())
.orIfSet("AB_ATTRIBUTERELATION.ID_VALUE", this._attributeIds, SqlBuilder.IN()))
.cell() || 0);
}
AttributeRelationQuery.prototype.getMaxCount = function ()
{
if (!this._objectType || !this._rowId)
throw new Error("AttributeRelationQuery: Object type and row id are required");
if (!this._attributeIds || this._attributeIds.length !== 1)
throw new Error("AttributeRelationQuery: You have to specify a single attribute id");
var attributeId = this._attributeIds[0];
var maxCount = newSelect("MAX_COUNT")
.from("AB_ATTRIBUTEUSAGE")
.where("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", attributeId)
.and("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", this._objectType)
.cell();
if (maxCount)
return Number(maxCount) || null;
return null;
}
AttributeRelationQuery.prototype.getMinCount = function ()
{
if (!this._objectType || !this._rowId)
throw new Error("AttributeRelationQuery: Object type and row id are required");
if (!this._attributeIds || this._attributeIds.length !== 1)
throw new Error("AttributeRelationQuery: You have to specify a single attribute id");
var attributeId = this._attributeIds[0];
return Number(newSelect("MIN_COUNT")
.from("AB_ATTRIBUTEUSAGE")
.where("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", attributeId)
.and("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", this._objectType)
.cell());
}
/**
* Inserts a new attribute relation.
*
* @param {String} pValue <p>
* Value of the attribute relation.
* @param {Boolean} [pOmitValidation=false] <p>
* If set to true, the current usage of the attribute and <br>
* max count won't be checked.<br>
* @return {Boolean} <p>
* True, if the attribute relation was inserted, false <br>
* if the count validation failed.<br>
*/
AttributeRelationQuery.prototype.insertAttribute = function (pValue, pOmitValidation)
{
if (!this._objectType || !this._rowId)
throw new Error("AttributeRelationQuery: Object type and row id are required for insert");
if (!this._attributeIds || this._attributeIds.length !== 1)
throw new Error("AttributeRelationQuery: You have to specify a single attribute id for insert");
var attributeId = this._attributeIds[0];
if (!pOmitValidation)
{
var maxCount = this.getMaxCount();
if (maxCount)
{
let timesUsed = this.getAttributeCount();
if (timesUsed >= maxCount)
return false;
}
}
var attrData = {
"AB_ATTRIBUTE_ID" : attributeId,
"OBJECT_ROWID" : this._rowId,
"OBJECT_TYPE" : this._objectType,
"DATE_NEW" : vars.get("$sys.date"),
"USER_NEW" : vars.get("$sys.user")
};
var type = AttributeUtil.getAttributeType(attributeId);
var valueField = AttributeTypeUtil.getDatabaseField(type);
if (valueField)
attrData[valueField] = pValue;
new SqlBuilder().insertFields(attrData, "AB_ATTRIBUTERELATION", "AB_ATTRIBUTERELATIONID");
return true;
}
/**
* deletes all attribute relations with the given rowId and objectType
*
* @return {Number} count of deleted rows
*/
AttributeRelationQuery.prototype.deleteAllAttributes = function ()
{
if (!this._rowId)
throw new Error("AttributeRelationQuery: Row id is required for delete");
return newWhere("AB_ATTRIBUTERELATION.OBJECT_ROWID", this._rowId)
.andIfSet("AB_ATTRIBUTERELATION.OBJECT_TYPE", this._objectType)
.deleteData();
}
/**
* Object representing one attribute relation in the database. Don't use this constructor in you own code!
* Instances of this should only be created by functions in this library.
*
* @param {String} pAttributeRelationId attribute relation id
* @param {String} pAttributeId attribute id
* @param {String} pValue value of the attribute
* @param {String} pAttributeName name of the attribute
* @param {String} pAttributeType type of the attribute
* @param {String} pObjectRowId rowId of the linked object
* @param {String} pObjectType context of the linked object
*/
function AttributeRelation (pAttributeRelationId, pAttributeId, pValue, pAttributeName, pAttributeType, pObjectRowId, pObjectType)
{
if (!pAttributeRelationId)
throw new Error("AttributeRelation: pAttributeRelationId must be provided");
this.attributeRelationId = pAttributeRelationId;
this.attributeId = pAttributeId;
this.value = pValue;
this.attributeName = pAttributeName;
this.attributeType = pAttributeType;
this.objectRowId = pObjectRowId;
this.objectType = pObjectType;
this.displayValue = undefined;
this.fullAttributeName = undefined;
}
/**
* updates the value of the attribute in the database
*
* @param {String} pValue the new value of the attribute relation
* @return {Boolean} currently the function always returns true (if some kind of validation is implemented in the future,
* it will return false if the validation fails)
*/
AttributeRelation.prototype.updateAttribute = function (pValue)
{
if (pValue == undefined || pValue == "")
throw new Error("AttributeRelation: no value provided for update");
var attrData = {
"DATE_EDIT" : vars.get("$sys.date"),
"USER_EDIT" : vars.get("$sys.user")
};
var valueField = AttributeTypeUtil.getDatabaseField(this.attributeType);
if (valueField)
attrData[valueField] = pValue;
newWhere("AB_ATTRIBUTERELATION.AB_ATTRIBUTERELATIONID", this.attributeRelationId)
.updateFields(attrData);
return true;
}
/**
* deletes the attribute relation from the database
*
* @param {Boolean} [pOmitValidation=false] if set to true, the function won't check if the min count prohibits the deletion
* @retun {Boolean} true if it was deleted and false if the min count doesn't allow the deletion
*/
AttributeRelation.prototype.deleteAttribute = function (pOmitValidation)
{
if (!pOmitValidation)
{
var attributeQuery = new AttributeRelationQuery(this.objectRowId, this.attributeId, this.objectType);
var minCount = attributeQuery.getMinCount();
if (minCount)
{
let timesUsed = attributeQuery.getAttributeCount();
if (timesUsed <= minCount)
return false;
}
}
newWhere("AB_ATTRIBUTERELATION.AB_ATTRIBUTERELATIONID", this.attributeRelationId)
.deleteData();
return true;
}