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; }