powerplatform-mcp
Version:
PowerPlatform Model Context Protocol server
629 lines (628 loc) • 32 kB
JavaScript
/**
* Service for entity metadata operations.
* Handles entity definitions, attributes, and relationships.
*/
export class EntityService {
client;
constructor(client) {
this.client = client;
}
/**
* Get metadata about an entity
* @param entityName The logical name of the entity
*/
async getEntityMetadata(entityName) {
const response = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')`);
// Remove Privileges property if it exists
if (response && typeof response === 'object' && 'Privileges' in response) {
delete response.Privileges;
}
return response;
}
/**
* Get metadata about entity attributes/fields
* @param entityName The logical name of the entity
*/
async getEntityAttributes(entityName) {
const selectProperties = [
'LogicalName',
'RequiredLevel',
'AttributeType',
].join(',');
// Make the request to get attributes
const response = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes?$select=${selectProperties}&$filter=AttributeType ne 'Virtual'`);
if (response && response.value) {
// First pass: Filter out attributes that end with 'yominame'
response.value = response.value.filter((attribute) => {
const logicalName = attribute.LogicalName || '';
return !logicalName.endsWith('yominame');
});
// Filter out attributes that end with 'name' if there is another attribute with the same name without the 'name' suffix
const baseNames = new Set();
const namesAttributes = new Map();
for (const attribute of response.value) {
const logicalName = attribute.LogicalName || '';
if (logicalName.endsWith('name') && logicalName.length > 4) {
const baseName = logicalName.slice(0, -4); // Remove 'name' suffix
namesAttributes.set(baseName, attribute);
}
else {
// This is a potential base attribute
baseNames.add(logicalName);
}
}
// Find attributes to remove that match the pattern
const attributesToRemove = new Set();
for (const [baseName, nameAttribute] of namesAttributes.entries()) {
if (baseNames.has(baseName)) {
attributesToRemove.add(nameAttribute);
}
}
response.value = response.value.filter(attribute => !attributesToRemove.has(attribute));
}
return response;
}
/**
* Get metadata about a specific entity attribute/field
* @param entityName The logical name of the entity
* @param attributeName The logical name of the attribute
*/
async getEntityAttribute(entityName, attributeName) {
return this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${attributeName}')`);
}
/**
* Get one-to-many relationships for an entity
* @param entityName The logical name of the entity
*/
async getEntityOneToManyRelationships(entityName) {
const selectProperties = [
'SchemaName',
'RelationshipType',
'ReferencedAttribute',
'ReferencedEntity',
'ReferencingAttribute',
'ReferencingEntity',
'ReferencedEntityNavigationPropertyName',
'ReferencingEntityNavigationPropertyName',
'CascadeConfiguration'
].join(',');
// Only filter by ReferencingAttribute in the OData query since startswith isn't supported
const response = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/OneToManyRelationships?$select=${selectProperties}&$filter=ReferencingAttribute ne 'regardingobjectid'`);
// Filter the response to exclude relationships with ReferencingEntity starting with 'msdyn_' or 'adx_'
if (response && response.value) {
response.value = response.value.filter((relationship) => {
const referencingEntity = relationship.ReferencingEntity || '';
return !(referencingEntity.startsWith('msdyn_') || referencingEntity.startsWith('adx_'));
});
}
return response;
}
/**
* Get many-to-many relationships for an entity
* @param entityName The logical name of the entity
*/
async getEntityManyToManyRelationships(entityName) {
const selectProperties = [
'SchemaName',
'RelationshipType',
'Entity1LogicalName',
'Entity2LogicalName',
'Entity1IntersectAttribute',
'Entity2IntersectAttribute',
'Entity1NavigationPropertyName',
'Entity2NavigationPropertyName'
].join(',');
return this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/ManyToManyRelationships?$select=${selectProperties}`);
}
/**
* Get all relationships (one-to-many and many-to-many) for an entity
* @param entityName The logical name of the entity
*/
async getEntityRelationships(entityName) {
const [oneToMany, manyToMany] = await Promise.all([
this.getEntityOneToManyRelationships(entityName),
this.getEntityManyToManyRelationships(entityName)
]);
return {
oneToMany,
manyToMany
};
}
/**
* Set the vector (SVG) icon used for an entity in the unified-interface app nav.
* The referenced web resource must already exist and be of type 11 (Vector).
*
* Dataverse metadata updates can't use PATCH — they require a full-replace PUT
* (UpdateEntityRequest semantics). So: fetch full entity metadata, modify the
* one field, PUT it back. MergeLabels=true preserves localized labels.
*
* @param entityName Logical name (e.g. 'br_property')
* @param iconVectorName Web-resource name (e.g. 'br_property_icon.svg')
* @param solutionName Optional solution to scope the metadata change to
*/
async setEntityIconVector(entityName, iconVectorName, solutionName) {
const metadata = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')`);
const body = {
...metadata,
'@odata.type': 'Microsoft.Dynamics.CRM.EntityMetadata',
IconVectorName: iconVectorName,
};
const headers = { 'MSCRM.MergeLabels': 'true' };
if (solutionName)
headers['MSCRM.SolutionUniqueName'] = solutionName;
await this.client.put(`api/data/v9.2/EntityDefinitions(${metadata.MetadataId})`, body, headers);
}
/**
* Create a string (Single Line of Text) attribute on an entity.
* @param entityName The logical name of the entity
* @param schemaName The schema name for the new attribute (e.g. br_hospitableid)
* @param displayName The display name for the new attribute
* @param maxLength Maximum length of the text field (default: 100)
* @param requiredLevel Required level: 'None', 'ApplicationRequired', or 'SystemRequired'
* @param description Optional description for the attribute
*/
async createStringAttribute(entityName, schemaName, displayName, maxLength = 100, requiredLevel = 'None', description, languageCode = 1045, solutionName) {
const body = {
'@odata.type': '#Microsoft.Dynamics.CRM.StringAttributeMetadata',
SchemaName: schemaName,
DisplayName: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }],
},
RequiredLevel: { Value: requiredLevel },
MaxLength: maxLength,
FormatName: { Value: 'Text' },
};
if (description) {
body.Description = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }],
};
}
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers);
return { attributeId: result?.entityId ?? 'created' };
}
/**
* Create a new custom entity (table) in Dataverse.
* Creates the entity shell with a single primary name (Single Line of Text) attribute.
* Use the existing `createStringAttribute`, `createDecimalAttribute`, `createLookupAttribute`
* helpers to add additional columns after creation.
*
* @param schemaName Schema name for the new entity (e.g. 'br_FinancialLine' — must start with the publisher prefix)
* @param displayName Singular display name (e.g. 'Financial Line')
* @param displayCollectionName Plural display name (e.g. 'Financial Lines')
* @param primaryNameSchemaName Schema name for the primary name attribute (e.g. 'br_Name')
* @param primaryNameDisplayName Display name for the primary name attribute (e.g. 'Name')
* @param description Optional description for the entity
* @param ownershipType 'UserOwned' or 'OrganizationOwned' (default UserOwned)
* @param hasActivities Whether the entity tracks activities (default false)
* @param hasNotes Whether the entity supports notes (default false)
* @param languageCode Language code for labels (default 1045 — Polish)
* @param solutionName Optional solution unique name to add the entity to
*/
async createEntity(schemaName, displayName, displayCollectionName, primaryNameSchemaName, primaryNameDisplayName, description, ownershipType = 'UserOwned', hasActivities = false, hasNotes = false, languageCode = 1045, solutionName) {
const label = (text) => ({
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [
{
'@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel',
Label: text,
LanguageCode: languageCode,
},
],
});
const primaryName = {
'@odata.type': 'Microsoft.Dynamics.CRM.StringAttributeMetadata',
SchemaName: primaryNameSchemaName,
IsPrimaryName: true,
RequiredLevel: { Value: 'None' },
MaxLength: 200,
FormatName: { Value: 'Text' },
DisplayName: label(primaryNameDisplayName),
};
const body = {
'@odata.type': 'Microsoft.Dynamics.CRM.EntityMetadata',
SchemaName: schemaName,
DisplayName: label(displayName),
DisplayCollectionName: label(displayCollectionName),
OwnershipType: ownershipType,
HasActivities: hasActivities,
HasNotes: hasNotes,
IsActivity: false,
Attributes: [primaryName],
};
if (description) {
body.Description = label(description);
}
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions`, body, headers);
return { entityId: result?.entityId ?? result?.MetadataId ?? 'created' };
}
/**
* Create a local Picklist (Choice / OptionSet) attribute on an entity.
*
* @param entityName The logical name of the entity
* @param schemaName Schema name for the new attribute
* @param displayName Display name
* @param options Array of { value: number, label: string } defining the picklist options
* @param requiredLevel Required level
* @param description Optional description
* @param languageCode Language code for labels
* @param solutionName Optional solution unique name
*/
async createPicklistAttribute(entityName, schemaName, displayName, options, requiredLevel = 'None', description, languageCode = 1045, solutionName) {
const mkLabel = (text) => ({
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: text, LanguageCode: languageCode }],
});
const body = {
'@odata.type': 'Microsoft.Dynamics.CRM.PicklistAttributeMetadata',
SchemaName: schemaName,
DisplayName: mkLabel(displayName),
RequiredLevel: { Value: requiredLevel },
OptionSet: {
'@odata.type': 'Microsoft.Dynamics.CRM.OptionSetMetadata',
IsGlobal: false,
OptionSetType: 'Picklist',
Options: options.map(o => ({
Value: o.value,
Label: mkLabel(o.label),
})),
},
};
if (description) {
body.Description = mkLabel(description);
}
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers);
return { attributeId: result?.entityId ?? 'created' };
}
/**
* Delete an attribute from an entity. Irreversible — the column and all data in it are dropped.
* Fails if any component (form, view, workflow, etc.) still depends on the attribute; use
* `checkDependencies` first if you need to investigate.
*
* @param entityName The logical name of the entity
* @param attributeName The logical name of the attribute to delete
*/
async deleteEntityAttribute(entityName, attributeName) {
await this.client.delete(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${attributeName}')`);
}
/**
* Create a Money (Currency) attribute on an entity.
* On the first Money column added to an entity, Dataverse automatically adds a
* `transactioncurrencyid` lookup to the entity if it doesn't already have one.
*
* @param entityName The logical name of the entity
* @param schemaName The schema name for the new attribute
* @param displayName The display name
* @param precisionSource 0 = SimpleFixed (use Precision), 1 = Pricing, 2 = Currency (default)
* @param precision Display precision when precisionSource is 0 (default 2)
* @param minValue Minimum allowed value
* @param maxValue Maximum allowed value
* @param requiredLevel Required level
* @param description Optional description
* @param languageCode Language code for labels
* @param solutionName Optional solution unique name
*/
async createMoneyAttribute(entityName, schemaName, displayName, precisionSource = 2, precision = 2, minValue = -100_000_000_000, maxValue = 100_000_000_000, requiredLevel = 'None', description, languageCode = 1045, solutionName) {
const body = {
'@odata.type': 'Microsoft.Dynamics.CRM.MoneyAttributeMetadata',
SchemaName: schemaName,
DisplayName: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }],
},
RequiredLevel: { Value: requiredLevel },
PrecisionSource: precisionSource,
Precision: precision,
MinValue: minValue,
MaxValue: maxValue,
};
if (description) {
body.Description = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }],
};
}
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers);
return { attributeId: result?.entityId ?? 'created' };
}
/**
* Create a decimal number attribute on an entity.
* Used for money/quantities when you don't want Dataverse Currency (which requires a TCURRENCY lookup).
*
* @param entityName The logical name of the entity
* @param schemaName The schema name for the new attribute
* @param displayName The display name
* @param precision Number of digits after the decimal (default 2, max 10)
* @param minValue Minimum allowed value (default -100_000_000_000)
* @param maxValue Maximum allowed value (default 100_000_000_000)
* @param requiredLevel Required level
* @param description Optional description
* @param languageCode Language code for labels
* @param solutionName Optional solution unique name
*/
async createDecimalAttribute(entityName, schemaName, displayName, precision = 2, minValue = -100_000_000_000, maxValue = 100_000_000_000, requiredLevel = 'None', description, languageCode = 1045, solutionName) {
const body = {
'@odata.type': 'Microsoft.Dynamics.CRM.DecimalAttributeMetadata',
SchemaName: schemaName,
DisplayName: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }],
},
RequiredLevel: { Value: requiredLevel },
Precision: precision,
MinValue: minValue,
MaxValue: maxValue,
};
if (description) {
body.Description = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }],
};
}
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers);
return { attributeId: result?.entityId ?? 'created' };
}
/**
* Create a DateTime attribute on an entity.
*
* @param entityName The logical name of the entity
* @param schemaName The schema name for the new attribute
* @param displayName The display name
* @param format 'DateOnly' or 'DateAndTime' (default 'DateOnly')
* @param behavior 'UserLocal', 'DateOnly', or 'TimeZoneIndependent' (default 'UserLocal')
* @param requiredLevel Required level
* @param description Optional description
* @param languageCode Language code for labels
* @param solutionName Optional solution unique name
*/
async createDateTimeAttribute(entityName, schemaName, displayName, format = 'DateOnly', behavior = 'UserLocal', requiredLevel = 'None', description, languageCode = 1045, solutionName) {
const body = {
'@odata.type': '#Microsoft.Dynamics.CRM.DateTimeAttributeMetadata',
SchemaName: schemaName,
DisplayName: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }],
},
RequiredLevel: { Value: requiredLevel },
Format: format,
DateTimeBehavior: { Value: behavior },
};
if (description) {
body.Description = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }],
};
}
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers);
return { attributeId: result?.entityId ?? 'created' };
}
/**
* Create an Integer (Whole Number) attribute on an entity.
*
* @param entityName The logical name of the entity
* @param schemaName The schema name for the new attribute
* @param displayName The display name
* @param minValue Minimum allowed value (default -2147483648)
* @param maxValue Maximum allowed value (default 2147483647)
* @param requiredLevel Required level
* @param description Optional description
* @param languageCode Language code for labels
* @param solutionName Optional solution unique name
*/
async createIntegerAttribute(entityName, schemaName, displayName, minValue = -2147483648, maxValue = 2147483647, requiredLevel = 'None', description, languageCode = 1045, solutionName) {
const body = {
'@odata.type': '#Microsoft.Dynamics.CRM.IntegerAttributeMetadata',
SchemaName: schemaName,
DisplayName: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }],
},
RequiredLevel: { Value: requiredLevel },
MinValue: minValue,
MaxValue: maxValue,
};
if (description) {
body.Description = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }],
};
}
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers);
return { attributeId: result?.entityId ?? 'created' };
}
/**
* Create a Boolean (Two Option / Yes/No) attribute on an entity.
*
* @param entityName The logical name of the entity
* @param schemaName The schema name for the new attribute
* @param displayName The display name
* @param trueLabel Label for the true/yes option (default 'Yes')
* @param falseLabel Label for the false/no option (default 'No')
* @param defaultValue Default value (default false)
* @param requiredLevel Required level
* @param description Optional description
* @param languageCode Language code for labels
* @param solutionName Optional solution unique name
*/
async createBooleanAttribute(entityName, schemaName, displayName, trueLabel = 'Yes', falseLabel = 'No', defaultValue = false, requiredLevel = 'None', description, languageCode = 1045, solutionName) {
const body = {
'@odata.type': '#Microsoft.Dynamics.CRM.BooleanAttributeMetadata',
SchemaName: schemaName,
DisplayName: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }],
},
RequiredLevel: { Value: requiredLevel },
DefaultValue: defaultValue,
OptionSet: {
TrueOption: {
Value: 1,
Label: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: trueLabel, LanguageCode: languageCode }],
},
},
FalseOption: {
Value: 0,
Label: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: falseLabel, LanguageCode: languageCode }],
},
},
},
};
if (description) {
body.Description = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }],
};
}
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers);
return { attributeId: result?.entityId ?? 'created' };
}
/**
* Create a Memo (Multi-Line Text) attribute on an entity.
*
* @param entityName The logical name of the entity
* @param schemaName The schema name for the new attribute
* @param displayName The display name
* @param maxLength Maximum text length (default 2000)
* @param requiredLevel Required level
* @param description Optional description
* @param languageCode Language code for labels
* @param solutionName Optional solution unique name
*/
async createMemoAttribute(entityName, schemaName, displayName, maxLength = 2000, requiredLevel = 'None', description, languageCode = 1045, solutionName) {
const body = {
'@odata.type': '#Microsoft.Dynamics.CRM.MemoAttributeMetadata',
SchemaName: schemaName,
DisplayName: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }],
},
RequiredLevel: { Value: requiredLevel },
MaxLength: maxLength,
FormatName: { Value: 'TextArea' },
};
if (description) {
body.Description = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }],
};
}
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers);
return { attributeId: result?.entityId ?? 'created' };
}
/**
* Create a lookup (N:1 relationship) attribute on an entity.
* Wraps the Dataverse `CreateOneToManyRelationship` action.
*
* @param referencingEntity The child entity that will hold the new lookup column (e.g. 'br_reservation')
* @param referencedEntity The parent entity the lookup points to (e.g. 'contact')
* @param relationshipSchemaName Schema name for the 1:N relationship (e.g. 'br_reservation_contact')
* @param lookupSchemaName Schema name for the lookup column itself (e.g. 'br_ContactId')
* @param displayName Display name shown on the lookup column
* @param requiredLevel Required level: 'None', 'ApplicationRequired', or 'SystemRequired'
* @param description Optional description for the lookup column
* @param cascadeDelete Cascade delete behavior (default 'RemoveLink' — deleting parent just nulls the lookup)
* @param languageCode Language code for labels (default 1045 — Polish)
* @param solutionName Optional solution unique name to add the component to
*/
async createLookupAttribute(referencingEntity, referencedEntity, relationshipSchemaName, lookupSchemaName, displayName, requiredLevel = 'None', description, cascadeDelete = 'RemoveLink', languageCode = 1045, solutionName) {
const emptyLabel = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [],
};
const displayLabel = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [
{
'@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel',
Label: displayName,
LanguageCode: languageCode,
},
],
};
const lookup = {
'@odata.type': 'Microsoft.Dynamics.CRM.LookupAttributeMetadata',
SchemaName: lookupSchemaName,
DisplayName: displayLabel,
RequiredLevel: { Value: requiredLevel },
};
if (description) {
lookup.Description = {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [
{
'@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel',
Label: description,
LanguageCode: languageCode,
},
],
};
}
const body = {
'@odata.type': 'Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata',
SchemaName: relationshipSchemaName,
ReferencedEntity: referencedEntity,
ReferencingEntity: referencingEntity,
AssociatedMenuConfiguration: {
Behavior: 'UseCollectionName',
Group: 'Details',
Label: emptyLabel,
Order: 10000,
},
CascadeConfiguration: {
Assign: 'NoCascade',
Delete: cascadeDelete,
Merge: 'NoCascade',
Reparent: 'NoCascade',
Share: 'NoCascade',
Unshare: 'NoCascade',
},
Lookup: lookup,
};
// POST to RelationshipDefinitions with the OneToMany metadata; Lookup is nested inside.
// Solution association goes through the standard MSCRM.SolutionUniqueName header.
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/RelationshipDefinitions`, body, headers);
return { attributeId: result?.AttributeId ?? result?.entityId ?? 'created' };
}
/**
* Get alternate keys defined on an entity.
* @param entityName The logical name of the entity
*/
async getEntityKeys(entityName) {
return this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Keys`);
}
/**
* Create an alternate key on an entity.
* @param entityName The logical name of the entity
* @param schemaName The schema name for the key
* @param displayName The display name for the key
* @param keyAttributes Array of attribute logical names that make up the key
*/
async createAlternateKey(entityName, schemaName, displayName, keyAttributes, languageCode = 1045, solutionName) {
const body = {
SchemaName: schemaName,
DisplayName: {
'@odata.type': 'Microsoft.Dynamics.CRM.Label',
LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }],
},
KeyAttributes: keyAttributes,
};
const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined;
const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Keys`, body, headers);
return { keyId: result?.entityId ?? 'created' };
}
}