CRM 2013:如何安排并发约会(使用约会和RecurringAppointmentMaster实体)?

时间:2018-03-29 21:23:03

标签: dynamics-crm dynamics-crm-2013 appointment capacity resource-scheduling

我们有一个使用BookRequest&的插件RescheduleRequest安排Appointment&的方法RecurringAppointmentMaster个实体。 最近,我的任务是实现在给定时间段内安排多个appts的能力。 因此,在研究这个问题时,我发现一些帖子涉及资源容量(工作时间)和在约会中将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中安排并发约会如此困难?

1 个答案:

答案 0 :(得分:2)

这就是我学到的难点,也许这会让别人感到沮丧。

我查看了数据库&注意到与约会相关联的activityparty实体都将努力字段设置为2139095040,我想知道这个数字来自哪里?在与CRM无关的帖子中,我发现2139095040意味着“正无限”。

我重新审视了他们谈论将工作量设置为1.0&的帖子。意识到他们都指的是ServiceAppointment实体(不是Appointment)然后我最终偶然发现了错误代码列表 Scheduling Error Codes

  

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

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

无论如何要解决这个问题,我删除了设置工作量= 1.0的逻辑,当BookRequestRescheduleRequest返回ErrorCode.ResourceBusy时,我检查容量与该时间段中安排的#appts &安培;如果剩余容量,我会通过使用创建或更新强制它超量预订。

    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更好的解决方案,因为现在我们不需要一次性按需工作流来更新现有数据。

我希望如果您需要这样做,这有助于节省您的时间。