You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CRM Dynamics 2016:如何限制商机关联笔记仅可附加单个文件?

限制Dynamics CRM 2016商机笔记仅允许单个附件的实现方案

当然可以实现这个需求!针对Dynamics CRM 2016里商机关联笔记的单文件限制,我给你两种常见的实现思路,分别适配不同的场景:

场景1:限制单条笔记只能有一个附件(增强默认行为)

虽然CRM 2016默认每条笔记(Annotation)只能关联一个附件,但你可以通过客户端脚本优化用户体验,比如阻止用户替换已有附件,或者在误操作时给出明确提示:

客户端JavaScript实现步骤

  1. 进入CRM后台的设置 > 自定义 > 自定义系统,找到**笔记(Annotation)**实体,打开它的主表单。
  2. 在表单编辑器中添加一个OnSave事件脚本,代码如下:
function validateSingleAttachment(executionContext) {
    const formContext = executionContext.getFormContext();
    const isExistingRecord = formContext.data.entity.getId() !== null;

    // 仅针对已有附件的笔记,阻止替换附件
    if (isExistingRecord) {
        const originalFileName = formContext.getAttribute("filename").getOriginalValue();
        const currentDocumentBody = formContext.getAttribute("documentbody").getValue();
        const originalDocumentBody = formContext.getAttribute("documentbody").getOriginalValue();

        if (originalFileName && currentDocumentBody && originalDocumentBody !== currentDocumentBody) {
            executionContext.getEventArgs().preventDefault();
            alert("该笔记已有附件,无法替换或添加新附件!");
        }
    }
}
  1. 绑定脚本时记得勾选传递执行上下文,保存并发布表单即可。

场景2:限制整个商机只能关联一个带附件的笔记

如果你的需求是单个商机只能附加一个文件(即只能有一条带附件的笔记),可以选择客户端脚本快速上线,或者用服务器端插件做严格限制(防止绕过客户端操作):

方法A:客户端JavaScript(商机表单)

这种方式适合快速落地,用户操作时实时给出提示:

  1. 打开商机的主表单,添加OnSave事件脚本:
function validateOpportunitySingleNote(executionContext) {
    const formContext = executionContext.getFormContext();
    const opportunityId = formContext.data.entity.getId().replace(/[{}]/g, ""); // 去除ID的大括号

    // 查询当前商机关联的带附件笔记数量
    const fetchXml = `<fetch version="1.0" output-format="xml-platform" mapping="logical">
        <entity name="annotation">
            <filter type="and">
                <condition attribute="objectid" operator="eq" value="${opportunityId}" />
                <condition attribute="documentbody" operator="not-null" />
            </filter>
        </entity>
    </fetchXml>`;

    Xrm.WebApi.retrieveMultipleRecords("annotation", "?fetchXml=" + encodeURIComponent(fetchXml)).then(
        (result) => {
            if (result.entities.length > 1) {
                executionContext.getEventArgs().preventDefault();
                alert("该商机只能附加一个文件,请删除多余的笔记后再保存!");
            }
        },
        (error) => {
            console.error("验证出错:", error.message);
            alert("验证附件数量时出错,请重试!");
        }
    );
}
  1. 绑定脚本时勾选传递执行上下文,保存发布表单。

方法B:服务器端插件(严格限制,防止绕过)

客户端脚本可以被开发者工具绕过,如果你需要严格管控,建议用C#插件实现:

using Microsoft.Xrm.Sdk;
using System;

public class RestrictOpportunitySingleNote : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        // 获取插件上下文和组织服务
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
        var service = serviceFactory.CreateOrganizationService(context.UserId);

        // 仅处理笔记实体的创建/更新操作
        if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity annotation)
        {
            // 检查笔记是否关联商机
            if (annotation.Contains("objectid") && annotation["objectid"] is EntityReference objRef && objRef.LogicalName == "opportunity")
            {
                var opportunityId = objRef.Id;

                // 查询商机关联的带附件笔记总数
                var fetchXml = $@"<fetch version='1.0' aggregate='true'>
                    <entity name='annotation'>
                        <attribute name='annotationid' alias='note_count' aggregate='count' />
                        <filter type='and'>
                            <condition attribute='objectid' operator='eq' value='{opportunityId}' />
                            <condition attribute='documentbody' operator='not-null' />
                        </filter>
                    </entity>
                </fetchXml>";

                var results = service.RetrieveMultiple(new FetchExpression(fetchXml));
                if (results.Entities.Count > 0)
                {
                    var noteCount = (int)((AliasedValue)results.Entities[0]["note_count"]).Value;

                    // 创建操作:如果已有1条及以上带附件笔记,阻止创建
                    // 更新操作:如果已有多条,且当前操作不是修改已有的那条,阻止更新
                    bool isCreate = context.MessageName.Equals("Create", StringComparison.OrdinalIgnoreCase);
                    bool isUpdate = context.MessageName.Equals("Update", StringComparison.OrdinalIgnoreCase);
                    bool exceedsLimit = (isCreate && noteCount >= 1) || 
                                        (isUpdate && noteCount > 1 && annotation.Id != ((EntityReference)context.PreEntityImages["PreImage"]).Id);

                    if (exceedsLimit)
                    {
                        throw new InvalidPluginExecutionException("该商机只能附加一个文件,无法添加或修改更多笔记!");
                    }
                }
            }
        }
    }
}

插件注册注意事项:

  • 注册在Annotation实体的Create和Update消息上,执行阶段选择PreValidation;
  • 针对Update消息,需要添加PreImage,包含annotationid字段,确保插件能识别是否是修改已有笔记。

内容的提问来源于stack exchange,提问作者Camilo López

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:23:51