CRM 2013: Как я могу планировать одновременные встречи (используя сущности Appointment & RecurringAppointmentMaster)?

У нас есть плагин, который использует BookRequest & RescheduleRequest Методы для планирования Appointment & RecurringAppointmentMaster юридические лица. Недавно мне было поручено реализовать возможность планирования нескольких приложений в заданном временном интервале. Поэтому, исследуя это, я обнаружил несколько постов, касающихся емкости ресурсов (в рабочее время) и установки поля Усилие ActivityParty на 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 ответ

Решение

Это то, что я усвоил нелегко, так что, возможно, это избавит кого-то от разочарования.

Я заглянул в базу данных и заметил, что для всех субъектов деятельности, связанных с назначениями, в поле усилия было установлено значение 2139095040, и мне стало интересно, откуда это число? В посте, не связанном с CRM, я узнал, что 2139095040 означает "положительная бесконечность".

Я повторно посетил посты, где они говорят об установке усилия на 1.0, и понял, что все они имеют в виду ServiceAppointment сущность (не Appointment) и тут я наконец наткнулся на список кодов ошибок Планирование кодов ошибок

DifferentEffort = Требуемая мощность этой службы не соответствует емкости ресурса {имя ресурса}.

Не совсем правда, но что угодно. Реальная проблема заключается в том, что объект назначения не ссылается на Сервис, поэтому емкость несуществующего сервиса - "Позитивная бесконечность" (2139095040). Установка Усилия, равной чему угодно, но 2139095040 выдает эту ошибку. Здесь на самом деле не проверяется емкость ресурса, а просто говорится, что несуществующая емкость службы должна быть равна = 2139095040.

В любом случае, чтобы обойти это, я удалил логику, которая устанавливает усилие = 1,0, и когда BookRequest или же RescheduleRequest возвращается ErrorCode.ResourceBusy Я проверяю Capacity vs # appts, запланированную в этом временном интервале, и если осталась свободная емкость, я вынуждаю ее перезаписывать, используя Create или Update.

    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 в любом случае, потому что теперь нам не нужен одноразовый рабочий процесс по требованию для обновления существующих данных.

Я надеюсь, что это поможет вам сэкономить время, если вам когда-нибудь понадобится это сделать.

Другие вопросы по тегам