2

我们有一个使用BookRequest&RescheduleRequest方法来调度Appointment&RecurringAppointmentMaster实体的插件。最近,我的任务是实现在给定时间段内安排多个应用程序的能力。所以在研究这个的过程中,我发现了一些关于资源容量(以工作时间计算)的帖子,并在 Appointment 中将 ActivityParty 的 Effort 字段设置为 1.0。我想,太好了,这很容易。

所以我改变了插件来存储努力:

            activityParty = new Entity("activityparty");
            activityParty["partyid"] = new EntityReference("systemuser", apptCaregiverId);
            activityParty["effort"] = (Double)1.0;

但是当我运行代码时,BookRequest 返回了这个让我感到困惑的错误:ErrorCode.DifferentEffort

我搜索了 ErrorCode.DifferentEffort, 2139095040, BookRequest,你的名字没有发现任何有用的东西。

这个错误到底是什么意思?为什么在 CRM 中安排并发约会如此困难?

4

1 回答 1

2

这是我艰难地学到的东西,所以也许它会让别人感到沮丧。

我查看了数据库并注意到与约会相关的活动方实体都将努力字段设置为 2139095040,我想知道这个数字是从哪里来的?在与 CRM 无关的帖子中,我发现 2139095040 的意思是“正无穷大”。

我重新访问了他们谈论将努力设置为 1.0 并意识到他们都指的是ServiceAppointment实体(不是Appointment)的帖子,然后我终于偶然发现了错误代码列表 调度错误代码

DifferentEffort = 此服务所需的容量与资源 {resource name} 的容量不匹配。

不完全是事实,但无论如何。真正的问题是约会实体没有引用服务,因此不存在的服务的容量是'Positive Infinity' (2139095040)。将 Effort 设置为除 2139095040 之外的任何值都会引发此错误。这里并不是真的检查资源的容量,只是说不存在的服务容量一定是=2139095040

无论如何,为了解决这个问题,我删除了设置努力 = 1.0 和何时BookRequestRescheduleRequest返回的逻辑ErrorCode.ResourceBusy,我检查了容量与该时间段中安排的 # 应用程序,如果有剩余容量,我通过使用创建或更新强制它超额预订.

    private Guid BookAppointment(Entity appointment, bool setState, out List<string> errors)
    {
        Guid apptId = Guid.Empty;

        try
        {
            BookRequest request = new BookRequest
            {
                Target = appointment
            };
            BookResponse booked = (BookResponse)this.orgService.Execute(request);
            apptId = ParseValidationResult(booked.ValidationResult, setState, appointment, true, out errors);
        }
        catch (Exception ex)
        {
            errors = new List<string> { ex.GetBaseException().Message };
        }

        return apptId;
    }

    private Guid RescheduleAppointment(Entity appointment, out List<string> errors)
    {   // used to reschedule non-recurring appt or appt in recurrence
        Guid apptId = Guid.Empty;
        try
        {
            RescheduleRequest request = new RescheduleRequest
            {
                Target = appointment
            };
            RescheduleResponse rescheduled = (RescheduleResponse)this.orgService.Execute(request);
            apptId = ParseValidationResult(rescheduled.ValidationResult, false, appointment, false, out errors);
        }
        catch (Exception ex)
        {
            errors = new List<string> { ex.GetBaseException().Message };
        }

        return apptId;
    }

    private Guid ParseValidationResult(ValidationResult result, bool setState, Entity appointment, Boolean addNew, out List<string> errors)
    {
        Guid apptId = result.ActivityId;
        errors = new List<string>();
        if (result.ValidationSuccess == true)
        {
            if (setState == true)
            {
                SetStateRequest state = new SetStateRequest();
                state.State = new OptionSetValue(3);   // Scheduled
                state.Status = new OptionSetValue(5);  // Busy
                state.EntityMoniker = new EntityReference("appointment", apptId);
                SetStateResponse stateSet = (SetStateResponse)this.orgService.Execute(state);
            }
        }
        else
        {
            String error;
            String errortxt;
            Boolean overbookAppt = true;
            foreach (var errorInfo in result.TraceInfo.ErrorInfoList)
            {
                bool unavailable = false;
                if (errorInfo.ErrorCode == "ErrorCode.ResourceNonBusinessHours")
                {
                    errortxt = "{0} is being scheduled outside work hours";
                }
                else if (errorInfo.ErrorCode == "ErrorCode.ResourceBusy")
                {
                    errortxt = "{0} is unavailable at this time";
                    unavailable = true;
                }
                else
                {
                    errortxt = "failed to schedule {0}, error code = " + errorInfo.ErrorCode;
                }
                Dictionary<Guid, String> providers;
                Dictionary<Guid, String> resources;
                DateTime start = DateTime.Now, end = DateTime.Now;
                Guid[] resourceIds = errorInfo.ResourceList.Where(r => r.EntityName == "equipment").Select(r => r.Id).ToList().ToArray();
                if (unavailable == true)
                {
                    if (appointment.LogicalName == "recurringappointmentmaster")
                    {
                        start = (DateTime)appointment["starttime"];
                        end = (DateTime)appointment["endtime"];
                    }
                    else
                    {
                        start = (DateTime)appointment["scheduledstart"];
                        end = (DateTime)appointment["scheduledend"];
                    }
                    Dictionary<Guid, Boolean> availability = GetAvailabilityOfResources(resourceIds, start, end);
                    resourceIds = availability.Where(a => a.Value == false).Select(t => t.Key).ToArray();   // get ids of all unavailable resources
                    if (resourceIds.Count() == 0)
                    {   // all resources still have capacity left at this timeslot - overbook appt timeslot
                        overbookAppt = true;
                    }   // otherwise at least some resources are booked up in this timeslot - return error
                }
                if (errortxt.Contains("{0}"))
                {   // include resource name in error msg
                    if (resourceIds.Count() > 0)
                    {
                        LoadProviderAndResourceInfo(resourceIds, out providers, out resources);
                        foreach (var resource in providers)
                        {
                            error = String.Format(errortxt, resource.Value);
                            errors.Add(error);
                        }
                        foreach (var resource in resources)
                        {
                            error = String.Format(errortxt, resource.Value);
                            errors.Add(error);
                        }
                    }
                }
                else
                {   // no place for name in msg - just store it
                    errors.Add(errortxt);
                    break;
                }
            }
            if (overbookAppt == true && errors.Count() == 0)
            {   // all resources still have capacity left at this timeslot & no other errors have been returned - create appt anyway
                if (addNew)
                {
                    appointment.Attributes.Remove("owner"); // Create message does not like when owner field is specified
                    apptId = this.orgService.Create(appointment);
                    if (setState == true)
                    {
                        SetStateRequest state = new SetStateRequest();
                        state.State = new OptionSetValue(3);   // Scheduled
                        state.Status = new OptionSetValue(5);  // Busy
                        state.EntityMoniker = new EntityReference("appointment", apptId);
                        SetStateResponse stateSet = (SetStateResponse)this.orgService.Execute(state);
                    }
                }
                else
                {
                    this.orgService.Update(appointment);
                }
            }
        }

        return apptId;
    }

    private Dictionary<Guid, Boolean> GetAvailabilityOfResources(Guid[] resourceIds, DateTime start, DateTime end)
    {
        Dictionary<Guid, Boolean> availability = new Dictionary<Guid, Boolean>();
        QueryMultipleSchedulesRequest scheduleRequest = new QueryMultipleSchedulesRequest();
        scheduleRequest.ResourceIds = resourceIds;
        scheduleRequest.Start = start;
        scheduleRequest.End = end;
        // TimeCode.Unavailable - gets appointments
        // TimeCode.Filter - gets resource capacity
        scheduleRequest.TimeCodes = new TimeCode[] { TimeCode.Unavailable, TimeCode.Filter };

        QueryMultipleSchedulesResponse scheduleResponse = (QueryMultipleSchedulesResponse)this.orgService.Execute(scheduleRequest);
        int index = 0;
        TimeInfo[][] timeInfo = new TimeInfo[scheduleResponse.TimeInfos.Count()][];
        foreach (var schedule in scheduleResponse.TimeInfos)
        {
            TimeInfo resourceCapacity = schedule.Where(s => s.SubCode == SubCode.ResourceCapacity).FirstOrDefault();
            Int32 capacity = (resourceCapacity != null) ? (Int32)resourceCapacity.Effort : 1;
            Int32 numAppts = schedule.Where(s => s.SubCode == SubCode.Appointment).Count();
            // resource is available if capacity is more than # appts in timeslot
            availability.Add(resourceIds[index++], (capacity > numAppts) ? true : false);
        }

        return availability;
    }

这确实是一个比设置 Effort = 1 更好的解决方案,因为现在我们不需要一次性的按需工作流来更新现有数据。

如果您需要这样做,我希望这可以帮助您节省一些时间。

于 2018-04-02T13:35:55.390 回答