Skip to content
Snippets Groups Projects
Commit b3145aa8 authored by Johannes Goderbauer's avatar Johannes Goderbauer
Browse files

Merge branch '#1060560-areaSearch' into 'master'

#1060560 area search

See merge request xrm/basic!346
parents 4c3ff245 2e8f19d9
No related branches found
No related tags found
No related merge requests found
Showing
with 438 additions and 9 deletions
/.aditoprj/cache
/.aditoprj/UUIDNameMap.txt
/.idea
/data
/others/db_changes/liqui_update.bat
/others/db_changes/liqui_reset.ps1
/others/jsdocOut
\ No newline at end of file
/others/jsdocOut
<?xml version="1.0" encoding="UTF-8"?>
<entity xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.3.17" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/entity/1.3.17">
<name>AroundLocation_entity</name>
<majorModelMode>DISTRIBUTED</majorModelMode>
<documentation>%aditoprj%/entity/AroundLocation_entity/documentation.adoc</documentation>
<onValidation>%aditoprj%/entity/AroundLocation_entity/onValidation.js</onValidation>
<recordContainer>dataless</recordContainer>
<entityFields>
<entityProvider>
<name>#PROVIDER</name>
</entityProvider>
<entityField>
<name>SearchRadius</name>
<title>Radius in km</title>
<contentType>NUMBER</contentType>
<outputFormat>#</outputFormat>
<inputFormat>#</inputFormat>
<state>EDITABLE</state>
<onValidation>%aditoprj%/entity/AroundLocation_entity/entityfields/searchradius/onValidation.js</onValidation>
</entityField>
<entityConsumer>
<name>Organisations</name>
<state>READONLY</state>
<dependency>
<name>dependency</name>
<entityName>Organisation_entity</entityName>
<fieldName>OrganisationsViaIndex</fieldName>
</dependency>
<children>
<entityParameter>
<name>OnlyShowContactIds_param</name>
<valueProcess>%aditoprj%/entity/AroundLocation_entity/entityfields/organisations/children/onlyshowcontactids_param/valueProcess.js</valueProcess>
</entityParameter>
</children>
</entityConsumer>
<entityActionField>
<name>Open</name>
<title>${ACTION_DO_OPEN}</title>
<onActionProcess>%aditoprj%/entity/AroundLocation_entity/entityfields/open/onActionProcess.js</onActionProcess>
<iconId>VAADIN:FOLDER_OPEN</iconId>
<state>DISABLED</state>
<stateProcess>%aditoprj%/entity/AroundLocation_entity/entityfields/open/stateProcess.js</stateProcess>
</entityActionField>
<entityField>
<name>ContactIds</name>
<valueProcess>%aditoprj%/entity/AroundLocation_entity/entityfields/contactids/valueProcess.js</valueProcess>
</entityField>
<entityParameter>
<name>LocationLat_param</name>
<expose v="true" />
<mandatory v="true" />
</entityParameter>
<entityParameter>
<name>LocationLon_param</name>
<expose v="true" />
<mandatory v="true" />
</entityParameter>
<entityField>
<name>RecordCountInformation</name>
<color>readonly-color</color>
<valueProcess>%aditoprj%/entity/AroundLocation_entity/entityfields/recordcountinformation/valueProcess.js</valueProcess>
</entityField>
<entityField>
<name>HitCount</name>
</entityField>
<entityField>
<name>TotalHitCount</name>
</entityField>
<entityParameter>
<name>OriginUid_param</name>
<expose v="true" />
<mandatory v="true" />
<documentation>%aditoprj%/entity/AroundLocation_entity/entityfields/originuid_param/documentation.adoc</documentation>
</entityParameter>
<entityProvider>
<name>#PROVIDER_AGGREGATES</name>
<useAggregates v="true" />
</entityProvider>
</entityFields>
<recordContainers>
<datalessRecordContainer>
<name>dataless</name>
<alias>Data_alias</alias>
</datalessRecordContainer>
</recordContainers>
</entity>
= AroundLocation_entity
Entity with a _dataless_ recordContainer that enables the user to enter a distance for a radius search and determinig the result records.
This is done as following:
. After a distance is entered, a indexsearch is run that determines the affected records that are within the given distance
. the UIDs of the records are collected
. the collected UIDs are passed to other entities via a param, and the other entites can filter for these uids
IMPORTANT: It's necessary to determine only the IDs and pass them to next entity because there is currently no way to filter the results by distance
in the next entity. This is due to a restriction of how solr handels such radius search requests.
== Related Entities
* `Organisation_entity`: This entity lists organisation that were found around a specific location.
== FAQ
_No questions have been added yet. You can help expanding this section by adding the first questions._
\ No newline at end of file
import("system.result");
import("system.util");
import("system.vars");
import("system.indexsearch");
import("system.neon");
var contactIds = [];
var radius = vars.get("$field.SearchRadius");
var lat = vars.get("$param.LocationLat_param");
var lon = vars.get("$param.LocationLon_param");
if (radius && lat && lon)
{
//filter our origin uid because otherwise we would have the origin organisation in our resultset
var excludedUid = vars.get("$param.OriginUid_param");
var pattern;
if (excludedUid)
{
var patternConfig = indexsearch.createPatternConfig();
var searchTerm = indexsearch.createTerm(excludedUid);
patternConfig.not(searchTerm);
pattern = indexsearch.buildPattern(patternConfig);
}
//spatial filter
var spatialSearchExtension = indexsearch.createSpatialSearchExtension()
.setDistance(radius)
.setCenterPoint(lat, lon);
/*
We need to limit the resulting records since we only collect ids and pass them as parameter via a consumer.
It would be bad for performance reasons to set no limit an e.g. pass 400k IDs there.
However increasing the RECORD_LIMIT does not necessarily increase the amount of loaded data by the indexsearch. There is another internal handled
limit of the indexsarch. The RECORD_LIMIT will not overwrite that internal limit.
For example: If the RECORD_LIMIT is 500, and the interal limit 250 only 250 records are returned by the indexsearch.
However, if the RECORD_LIMIT is 100, and the interal limit 250 only 100 records are returned by the indexsearch.
*/
var RECORD_LIMIT = 250;
var indexQuery = indexsearch.createIndexQuery()
.addExtension(spatialSearchExtension)
.setRows(RECORD_LIMIT)
.addIndexGroups("Organisation")
.addResultIndexFields([indexsearch.FIELD_ID]);
if (pattern)
indexQuery.setPattern(pattern);
var indexResult = indexsearch.searchIndex(indexQuery);
if (indexResult.TOTALHITS > 0)
contactIds = indexResult.HITS.map(function (e){
return e[indexsearch.FIELD_ID];
});
neon.setFieldValue("$field.HitCount", contactIds.length);
neon.setFieldValue("$field.TotalHitCount", indexResult.TOTALHITS);
}
result.string(JSON.stringify(contactIds));
\ No newline at end of file
import("system.vars");
import("system.neon");
var contactIds = vars.get("$field.ContactIds");
if (contactIds)
contactIds = JSON.parse(contactIds);
neon.openContext("Organisation", "OrganisationFilter_view", contactIds, neon.OPERATINGSTATE_SEARCH, null, null);
//neon.openContext("Organisation", null, contactIds, null, null, null);
\ No newline at end of file
import("system.vars");
import("system.result");
import("system.neon");
var res;
var hitCount = vars.get("$field.HitCount");
if (vars.get("$sys.validationerrors") || hitCount == null || hitCount == "" || parseInt(hitCount) == 0)
res = neon.COMPONENTSTATE_DISABLED;
else
res = neon.COMPONENTSTATE_EDITABLE;
result.string(res);
\ No newline at end of file
import("system.vars");
import("system.util");
import("system.result");
var contactIds = vars.get("$field.ContactIds");
if (contactIds)
contactIds = JSON.parse(contactIds);
else
contactIds = [];
if (contactIds.length == 0)
contactIds = ["###dummy###" + util.getNewUUID() + "###dummy###"];
result.string(JSON.stringify(contactIds));
\ No newline at end of file
This parameter is used to exclude ourself from the search results.
\ No newline at end of file
import("system.translate");
import("system.vars");
import("system.result");
import("system.logging");
var displayedHitCount = vars.get("$field.HitCount");
var totalHitCount = vars.get("$field.TotalHitCount");
var res;
if (displayedHitCount && totalHitCount)
{
if (parseInt(displayedHitCount) == 0)
{
res = "";
}
else if (parseInt(displayedHitCount) == 1)
{
res = translate.withArguments("${DISPLAY_AND_OPEN_ONE_HIT}", [displayedHitCount]);
}
else if (displayedHitCount == totalHitCount)
{
res = translate.withArguments("${DISPLAY_AND_OPEN_%0_HITS}", [displayedHitCount]);
}
else
{
res = translate.withArguments("${DISPLAY_AND_OPEN_%0_OF_%1_HITS}", [displayedHitCount, totalHitCount]);
}
}
else
res = "";
result.string(res);
\ No newline at end of file
import("system.vars");
import("system.result");
import("system.translate");
var value = vars.get("$local.value");
var res;
if (value)
{
value = parseInt(value);
if (value < 1)
res = translate.withArguments("The radius has to be at least %0.", ["1"]);
else if (value > 100)
res = translate.withArguments("The radius has to be %0 or lesser.", ["100"]);
}
if (res)
result.string(res);
\ No newline at end of file
import("system.translate");
import("system.vars");
import("system.result");
var res;
var lat = vars.get("$param.LocationLat_param");
var lon = vars.get("$param.LocationLon_param");
if (lat == null || lon == null)
{
res = translate.text("The given starting location is empty. An area search can not be performed.");
}
if (res)
result.string(res);
\ No newline at end of file
......@@ -1107,9 +1107,6 @@
<name>ORDERTYPE.displayValue</name>
<expression>%aditoprj%/entity/Order_entity/recordcontainers/db/recordfieldmappings/ordertype.displayvalue/expression.js</expression>
</dbRecordFieldMapping>
<dbRecordFieldMapping>
<name>COUNT.value</name>
</dbRecordFieldMapping>
<aggregateFieldDbMapping>
<name>COUNT_aggregate.value</name>
<recordfield>SALESORDER.SALESORDERID</recordfield>
......
import("system.vars");
import("system.question");
var r = vars.get("$field.#CONTENTTITLE");
question.showMessage(r);
\ No newline at end of file
......@@ -1171,12 +1171,75 @@
<title>Count</title>
</entityAggregateField>
<entityProvider>
<name>indexP</name>
<name>OrganisationsViaIndex</name>
<documentation>%aditoprj%/entity/Organisation_entity/entityfields/organisationsviaindex/documentation.adoc</documentation>
<recordContainer>index</recordContainer>
<dependencies>
<entityDependency>
<name>e60b8983-166d-4280-a1a5-f990ad77eeb9</name>
<entityName>AroundLocation_entity</entityName>
<fieldName>Organisations</fieldName>
<isConsumer v="false" />
</entityDependency>
</dependencies>
<children>
<entityParameter>
<name>ExcludeOrganisationsByPersonId</name>
<expose v="false" />
</entityParameter>
<entityParameter>
<name>AttributeId_param</name>
<expose v="false" />
</entityParameter>
<entityParameter>
<name>AttributeKeyId_param</name>
<expose v="false" />
</entityParameter>
<entityParameter>
<name>DuplicateActionsControl_param</name>
<expose v="false" />
</entityParameter>
<entityParameter>
<name>DuplicateCurrentContactId_param</name>
<expose v="false" />
</entityParameter>
<entityParameter>
<name>ExcludedContactIds_param</name>
<expose v="false" />
</entityParameter>
<entityParameter>
<name>OnlyOwnSupervised_param</name>
<expose v="false" />
</entityParameter>
<entityParameter>
<name>OnlyShowContactIds_param</name>
<expose v="true" />
</entityParameter>
<entityParameter>
<name>OrganisationType_param</name>
<expose v="false" />
</entityParameter>
<entityParameter>
<name>WithPrivate_param</name>
<expose v="true" />
</entityParameter>
</children>
</entityProvider>
<entityActionField>
<name>openAroundLocation</name>
<documentation>%aditoprj%/entity/Organisation_entity/entityfields/openaroundlocation/documentation.adoc</documentation>
<title>Radius Search</title>
<onActionProcess>%aditoprj%/entity/Organisation_entity/entityfields/openaroundlocation/onActionProcess.js</onActionProcess>
<isSelectionAction v="true" />
<iconId>VAADIN:LOCATION_ARROW_CIRCLE_O</iconId>
<state>DISABLED</state>
<stateProcess>%aditoprj%/entity/Organisation_entity/entityfields/openaroundlocation/stateProcess.js</stateProcess>
</entityActionField>
<entityField>
<name>ORGANISATION_OBJECTTYPE</name>
<valueProcess>%aditoprj%/entity/Organisation_entity/entityfields/organisation_objecttype/valueProcess.js</valueProcess>
<name>STANDARD_LAT</name>
</entityField>
<entityField>
<name>STANDARD_LON</name>
</entityField>
<entityProvider>
<name>#PROVIDER_AGGREGATES</name>
......@@ -1393,6 +1456,14 @@
<recordfield>ORGANISATION.ORGANISATIONID</recordfield>
<aggregateType>COUNT</aggregateType>
</aggregateFieldDbMapping>
<dbRecordFieldMapping>
<name>STANDARD_LAT.value</name>
<recordfield>ADDRESS.LAT</recordfield>
</dbRecordFieldMapping>
<dbRecordFieldMapping>
<name>STANDARD_LON.value</name>
<recordfield>ADDRESS.LON</recordfield>
</dbRecordFieldMapping>
</recordFieldMappings>
<filterExtensions>
<filterExtensionSet>
......@@ -1407,6 +1478,7 @@
<indexRecordContainer>
<name>index</name>
<configMode>INDEXGROUP_DEFINITION</configMode>
<patternExtensionProcess>%aditoprj%/entity/Organisation_entity/recordcontainers/index/patternExtensionProcess.js</patternExtensionProcess>
<indexRecordAlias>Data_alias</indexRecordAlias>
<query>%aditoprj%/entity/Organisation_entity/recordcontainers/index/query.js</query>
<affectedTables>
......@@ -1497,6 +1569,11 @@
</additionalFieldNameAliases>
<isMultiValued v="true" />
</indexRecordFieldMapping>
<indexRecordFieldMapping>
<name>ADDRESS_ID.displayValue</name>
<indexFieldType>ADDRESS</indexFieldType>
<isGlobalSearchField v="false" />
</indexRecordFieldMapping>
</indexFieldMappings>
</indexRecordContainer>
</recordContainers>
......
Only available if location-data is present.
\ No newline at end of file
import("system.entities");
import("system.vars");
import("system.neon");
var rowConfig = entities.createConfigForLoadingRows()
.fields(["STANDARD_LAT", "STANDARD_LON"])
.entity("Organisation_entity")
// .entity(vars.get("$sys.currententityname"))
.uid(vars.get("$sys.uid"));
var row = entities.getRow(rowConfig);
//todo: use read entity and comment why (data is not loaded)
var lat = row["STANDARD_LAT"];
var lon = row["STANDARD_LON"];
var params = {
OriginUid_param: vars.get("$sys.uid"),
LocationLat_param: lat,
LocationLon_param: lon
}
neon.openContext("AroundLocation", "AroundOrganisationLocation_view", null, null, params, null);
\ No newline at end of file
import("system.result");
import("system.neon");
import("system.vars");
import("system.entities");
//todo: only visible if location tracking is enabled
//todo: use read entity and comment why (data is not loaded)
var rowConfig = entities.createConfigForLoadingRows()
.fields(["STANDARD_LAT", "STANDARD_LON"])
.entity(vars.get("$sys.currententityname"))
.uid(vars.get("$sys.uid"));
var row = entities.getRow(rowConfig);
var lat = row["STANDARD_LAT"];
var lon = row["STANDARD_LON"];
var res;
if (lat && lon)
res = neon.COMPONENTSTATE_AUTO;
else
res = neon.COMPONENTSTATE_DISABLED;
result.string(res);
\ No newline at end of file
Provides organisations via the index recordContainer and not the default (database) recorcContainer.
WARNING: Not all Parameters are supported in the index recordContainer.
Especially the parameters that are used for filtering.
If these parameters are necessary they need to be implemented in the `patternExtensionProcess` of the IndexRecordContainer.
\ No newline at end of file
import("Entity_lib");
import("system.result");
import("system.vars");
import("system.indexsearch");
import("system.logging");
var patternConfig = indexsearch.createPatternConfig();
// filter privat company if it is not needed
if (vars.exists("$param.WithPrivate_param") && vars.getString("$param.WithPrivate_param") != "true")
{
patternConfig.not(indexsearch.createTerm("0").setIndexField("organisationid_value"));
}
ParameterUtils.handleNotEmptyEntityParam("$param.OnlyShowContactIds_param", function(pContactIds){
var contactIdsToFilter = JSON.parse(pContactIds);
var filterGroup = indexsearch.createGroupTerm();
contactIdsToFilter.forEach(function (contactId){
filterGroup.or(indexsearch.createTerm(contactId).setIndexField(indexsearch.FIELD_ID));
});
patternConfig.and(filterGroup);
});
var pattern = indexsearch.buildPattern(patternConfig);
logging.log("Pattern:" + pattern);
result.string(pattern);
......@@ -28,7 +28,7 @@ var querySelect = newSelect([
sqlHelper.concat([sqlHelper.cast("defaultAddress.LAT", SQLTYPES.VARCHAR, 16), sqlHelper.cast("defaultAddress.LON", SQLTYPES.VARCHAR, 16)], ","),
//additional indexed fields
"ORGANISATION.NAME",
"ORGANISATION.ORGANISATIONID",
sqlHelper.trim("ORGANISATION.ORGANISATIONID"),//trim to enable filter patterns like: >> -organisationid_value:0<<
"CONTACT.CONTACTID",
"ORGANISATION.CUSTOMERCODE",
"ADDRESS.ADDRESS",
......@@ -36,7 +36,10 @@ var querySelect = newSelect([
"ADDRESS.ZIP",
"ADDRESS.CITY",
"COMMUNICATION.ADDR",
"PHONE.ADDR"
"PHONE.ADDR",
sqlHelper.concat([sqlHelper.concat(["defaultAddress.ADDRESS", "defaultAddress.BUILDINGNO"])
,sqlHelper.concat(["defaultAddress.COUNTRY", "defaultAddress.ZIP", "defaultAddress.CITY"])
], " - ")
])
.from("ORGANISATION")
.join("CONTACT", "CONTACT.ORGANISATION_ID = ORGANISATION.ORGANISATIONID and CONTACT.PERSON_ID is null")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment