예제 #1
0
        /// <summary>
        /// Метод запуска операции на выполнение, «входная» точка операции
        /// ПЕРЕГРУЖЕННЫЙ, СТРОГО ТИПИЗИРОВАННЫЙ МЕТОД
        /// ВЫЗЫВАЕТСЯ ЯДРОМ АВТОМАТИЧЕСКИ
        /// </summary>
        /// <param name="request">Запрос на выполнение операции</param>
        /// <param name="context">Контекст выполнения операции</param>
        /// <returns>Результат выполнения</returns>
        public virtual XResponse Execute(SaveObjectInternalRequest request, IXExecutionContext context)
        {
            // #1: Проверка прав
            // Примечание: делаем это здесь, а не в гварде, ради человеческой диагностики
            XSecurityManager sec_man    = XSecurityManager.Instance;
            IEnumerator      enumerator = request.DataSet.GetModifiedObjectsEnumerator(false);
            DomainObjectData xobj;

            while (enumerator.MoveNext())
            {
                xobj = (DomainObjectData)enumerator.Current;
                if (xobj.ToDelete)
                {
                    sec_man.DemandDeleteObjectPrivilege(xobj);
                }
                else
                {
                    sec_man.DemandSaveObjectPrivilege(xobj);
                }
            }

            // #2: Запись данных
            XStorageGateway.Save(context, request.DataSet, request.TransactionID);

            // #3: Вызовем post-call-процедуры (если таковые определены)
            //executePostCalls(request.PostCalls, context);

            // Специального результата операция не возвращает
            return(new XResponse());
        }
예제 #2
0
        public XResponse Execute(MoveObjectsRequest request, IXExecutionContext context)
        {
            DomainObjectDataSet dataSet = new DomainObjectDataSet(context.Connection.MetadataManager.XModel);

            foreach (Guid oid in request.SelectedObjectsID)
            {
                DomainObjectData xobj = dataSet.CreateStubLoaded(request.SelectedObjectType, oid, -1);
                xobj.SetUpdatedPropValue(request.ParentPropName, request.NewParent);
                // для объекта проверим права
                XSecurityManager.Instance.DemandSaveObjectPrivilege(xobj);
            }
            XStorageGateway.Save(context, dataSet, Guid.NewGuid());

            return(new XResponse());
        }
예제 #3
0
        /// <summary>
        /// Метод выполнения операции, типизированная реализация
        /// </summary>
        ///	<param name="oRequest">Объект-запрос на выполнение операции</param>
        /// <param name="oContext">Представление контекста выполнения операции</param>
        /// <returns>
        /// Экземпляр объекта-результата выполнения операции
        /// </returns>
        public XResponse Execute(XDeleteObjectRequest oRequest, IXExecutionContext oContext)
        {
            // Проверка параметров запроса
            XRequest.ValidateRequiredArgument(oRequest.TypeName, "XDeleteObjectRequest.TypeName");
            XRequest.ValidateRequiredArgument(oRequest.ObjectID, "XDeleteObjectRequest.ObjectID");

            DomainObjectData objData = DomainObjectData.CreateStubLoaded(oContext.Connection, oRequest.TypeName, oRequest.ObjectID);
            XObjectRights    rights  = XSecurityManager.Instance.GetObjectRights(objData);

            if (!rights.AllowDelete)
            {
                throw new XSecurityException(String.Format(
                                                 "Выполнение операции невозможно: нет прав на удаление объекта \"{0}\" ({1}[oid='{2}'])",
                                                 objData.TypeInfo.Description,
                                                 oRequest.TypeName,
                                                 oRequest.ObjectID
                                                 ));
            }

            // Вызываем метод подсистемы Storage (уровень Level-2); экземпляр
            // Storage представлен в рамках контекста выполнения опеарции
            return(new XDeleteObjectResponse(XStorageGateway.Delete(oContext, oRequest.TypeName, oRequest.ObjectID)));
        }
예제 #4
0
        /// <summary>
        /// Типизированная реализация
        /// </summary>
        /// <param name="request">Запрос на выполнение операции</param>
        /// <param name="context">Контекст выполнения операции</param>
        /// <returns>Результат выполнения</returns>
        public XResponse Execute(UpdateActivityStateRequest request, IXExecutionContext context)
        {
            //для того, чтобы изменение прошло от именя сотрудника, переданного в request.Initiator,
            //прейдется подменить CurrentPrincipal

            // для начала запомним текущий
            IPrincipal originalPrincipal = Thread.CurrentPrincipal;

            try
            {
                // если подсунули нам инициатора, вытащим имя пользователя и подменим CurrentPrincipal
                {
                    var ds = new DomainObjectDataSet(context.Connection.MetadataManager.XModel);

                    if (request.Initiator != Guid.Empty)
                    {
                        var employee = ds.Load(context.Connection, "Employee", request.Initiator);
                        var userID   = employee.GetLoadedPropValue("SystemUser");
                        if (userID == DBNull.Value)
                        {
                            throw new XBusinessLogicException("Сотрудник не является пользователем системы");
                        }
                        var user = ds.Load(context.Connection, "SystemUser", (Guid)userID);

                        Thread.CurrentPrincipal = new GenericPrincipal(
                            new GenericIdentity((string)user.GetLoadedPropValue("Login")),
                            new string[] { "XUser" });
                    }
                }

                // собственно внесем изменение
                {
                    var ds = new DomainObjectDataSet(context.Connection.MetadataManager.XModel);

                    // Загрузим объект
                    var activity = ds.Load(context.Connection, "Folder", request.Activity);

                    activity.SetUpdatedPropValue("State", request.NewState);
                    // Если задано, то и описание обновим
                    if (!String.IsNullOrEmpty(request.Description))
                    {
                        var description = activity.GetLoadedPropValueOrLoad(context.Connection, "Description");

                        activity.SetUpdatedPropValue(
                            "Description",
                            description == DBNull.Value || string.IsNullOrEmpty((string)description)
                                                                ? request.Description
                                                                : string.Format("{0}\n{1}", (string)description, request.Description)
                            );
                    }

                    XStorageGateway.Save(context, ds, Guid.NewGuid());
                }
            }
            finally
            {
                // в любом случае вернем все как было
                Thread.CurrentPrincipal = originalPrincipal;
            }

            return(new XResponse());
        }
예제 #5
0
        public XResponse Execute(MoveFolderRequest request, IXExecutionContext context)
        {
            XDbCommand          cmd;
            DomainObjectDataSet dataSet = new DomainObjectDataSet(context.Connection.MetadataManager.XModel);

            foreach (Guid oid in request.ObjectsID)
            {
                DomainObjectData xobj = dataSet.CreateStubLoaded("Folder", oid, -1);
                if (request.NewParent != Guid.Empty)
                {
                    // задана родительская папка
                    xobj.SetUpdatedPropValue("Parent", request.NewParent);
                    // родительская папка задана, однако, она может принадлежать другому клиенту и/или типу проектных затрат
                    cmd = context.Connection.CreateCommand(@"
						SELECT 
							CASE WHEN f1.Customer <> f2.Customer THEN f1.Customer ELSE cast(NULL as uniqueidentifier) END AS Customer, 
							CASE WHEN f1.ActivityType <> f2.ActivityType THEN f1.ActivityType ELSE cast(NULL as uniqueidentifier) END AS ActivityType
						FROM Folder f1, Folder f2
						WHERE f1.ObjectID = @NewParentID AND f2.ObjectID = @ObjectID 
							AND (f1.Customer <> f2.Customer OR f1.ActivityType <> f2.ActivityType)
						"                        );
                    cmd.Parameters.Add("NewParentID", DbType.Guid, ParameterDirection.Input, false, request.NewParent);
                    cmd.Parameters.Add("ObjectID", DbType.Guid, ParameterDirection.Input, false, oid);
                    using (IDataReader reader = cmd.ExecuteReader())
                    {
                        int nIndex;
                        if (reader.Read())
                        {
                            nIndex = reader.GetOrdinal("Customer");
                            if (!reader.IsDBNull(nIndex))
                            {
                                xobj.SetUpdatedPropValue("Customer", reader.GetGuid(nIndex));
                            }
                            nIndex = reader.GetOrdinal("ActivityType");
                            if (!reader.IsDBNull(nIndex))
                            {
                                xobj.SetUpdatedPropValue("ActivityType", reader.GetGuid(nIndex));
                            }
                        }
                    }
                    // Далее мы проверяем следующее:
                    //	каталог может быть подчинен папке любого типа, однако проект только проекту,
                    //	а пресейл и тендер вообще никому
                    cmd.CommandText = @"
						SELECT f1.Type AS FolderType, f2.Type AS ParentFolderType, 
							f1.LIndex, f1.RIndex,
							f2.LIndex AS ParentLIndex, f2.RIndex AS ParentRIndex
						FROM Folder f1, Folder f2
						WHERE f1.ObjectID = @ObjectID AND f2.ObjectID = @NewParentID
						"                        ;
                    // Примечание: используем созданную ранее команду с параметрами NewParentID и ObjectID
                    using (IDataReader reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            FolderTypeEnum folderType       = (FolderTypeEnum)reader.GetInt16(reader.GetOrdinal("FolderType"));
                            FolderTypeEnum parentFolderType = (FolderTypeEnum)reader.GetInt16(reader.GetOrdinal("ParentFolderType"));
                            // Если у папки не изменилась ссылка на организацию клиента, то проверим, что ее не переносят в одну из дочерних папок
                            if (!xobj.HasUpdatedProp("Customer"))
                            {
                                // Примечание: LIndex/RIndex будут пересчитываться в триггере
                                int nLIndex       = reader.GetInt32(reader.GetOrdinal("LIndex"));
                                int nRIndex       = reader.GetInt32(reader.GetOrdinal("RIndex"));
                                int nParentLIndex = reader.GetInt32(reader.GetOrdinal("ParentLIndex"));
                                int nParentRIndex = reader.GetInt32(reader.GetOrdinal("ParentRIndex"));
                                // Проверим, что новый родитель не является дочерним (рекурсивно) узлом переносимой папки
                                if (nParentLIndex >= nLIndex && nParentRIndex <= nRIndex)
                                {
                                    throw new XBusinessLogicException(FolderTypeEnumItem.GetItem(folderType).Description + " не может быть перенесен в подчиненный " + FolderTypeEnumItem.GetItem(parentFolderType).Description.ToLower());
                                }
                            }
                            if (folderType == FolderTypeEnum.Project)
                            {
                                if (parentFolderType != FolderTypeEnum.Project)
                                {
                                    throw new XBusinessLogicException("Проект не может быть перенесен в " + FolderTypeEnumItem.GetItem(parentFolderType).Description.ToLower());
                                }
                            }
                            else if (folderType == FolderTypeEnum.Tender || folderType == FolderTypeEnum.Presale)
                            {
                                throw new XBusinessLogicException("Тендер (тендерная активность) и пресейл (пресейл-активноть) не могут быть перенесены в папку");
                            }
                        }
                    }
                }
                else
                {
                    // перенос в корень
                    xobj.SetUpdatedPropValue("Parent", DBNull.Value);
                    // однако при этом мог измениться клиент или тип проектных затрат
                    if (request.NewActivityType != Guid.Empty)
                    {
                        // если изменился тип проектных затрат, то ссылка на клиента также должна быть задана (даже если он не изменился)
                        if (request.NewCustomer == Guid.Empty)
                        {
                            throw new ArgumentException("Если задана ссылка на тип проектных затрат, то должна быть задана ссылка на организацию-клиента");
                        }

                        // также нужно проверить, что выбранный типа проектных затрат поддерживает тип переносимой папки
                        cmd = context.Connection.CreateCommand(@"
							SELECT 1 FROM ActivityType 
							WHERE ObjectID = @ActivityTypeID AND FolderType & (SELECT [Type] FROM Folder WHERE ObjectID = @ObjectID) > 0
							"                            );
                        cmd.Parameters.Add("ActivityTypeID", DbType.Guid, ParameterDirection.Input, false, request.NewActivityType);
                        cmd.Parameters.Add("ObjectID", DbType.Guid, ParameterDirection.Input, false, oid);
                        if (cmd.ExecuteScalar() == null)
                        {
                            throw new XBusinessLogicException("Перенос папки невозможен. Выбранный тип проектных затрат не может содержать папку переносимого типа.");
                        }

                        xobj.SetUpdatedPropValue("ActivityType", request.NewActivityType);
                        xobj.SetUpdatedPropValue("Customer", request.NewCustomer);
                    }
                    else if (request.NewCustomer != Guid.Empty)
                    {
                        // Если выбрали организацию без изменения типа проектных затрат, то подразумевается, что он (ActivityTyoe) остается прежним,
                        // однако такое возможно только при переносе либо между организациями-клиентами, либо между организациями-владельцами.
                        cmd = context.Connection.CreateCommand(@"
							SELECT 1 FROM Organization c1, Organization c2 
							WHERE c1.ObjectID = (SELECT Customer FROM Folder WHERE ObjectID = @ObjectID)
								AND c2.ObjectID = @NewCustomerID AND c1.Home <> c2.Home
							"                            );
                        cmd.Parameters.Add("NewCustomerID", DbType.Guid, ParameterDirection.Input, false, request.NewCustomer);
                        cmd.Parameters.Add("ObjectID", DbType.Guid, ParameterDirection.Input, false, oid);
                        if (cmd.ExecuteScalar() != null)
                        {
                            throw new XBusinessLogicException("Перенос папки невозможен. Для переноса папок между разными типами проектных затрат следует выбрать узел, соответствующий требуемому типу проектных затрат.");
                        }

                        xobj.SetUpdatedPropValue("Customer", request.NewCustomer);
                    }
                    else
                    {
                        // Parent = Null, Customer = Null, ActivityType = Null - такого быть не может
                        throw new ArgumentException("При незаданной ссылке на родительскую папку должны быть заданы ссылки на Клиента и/или Тип проектных затрат");
                    }
                }

                XSecurityManager.Instance.DemandSaveObjectPrivilege(xobj);
            }
            XStorageGateway.Save(context, dataSet, Guid.NewGuid());

            // если все сохранилось хорошо, то отработали триггеры, изменяющие LIndex/RIndex, и возможно Customer.
            // Если у переносимой папки изменилась ссылка на тип проектных затрат ,
            // то необходимо изменить эту ссылку всем подчиненным папкам (если они есть)
            StringBuilder cmdBuilder = new StringBuilder();

            cmdBuilder.AppendFormat(
                @"UPDATE f
SET f.ActivityType = p.ActivityType
FROM Folder f
	JOIN Folder p ON f.LIndex > p.LIndex AND f.RIndex < p.RIndex AND f.Customer = p.Customer
WHERE p.ObjectID IN ("
                );
            cmd = context.Connection.CreateCommand();
            string sParamName;
            int    nParamIndex = 0;

            foreach (Guid oid in request.ObjectsID)
            {
                ++nParamIndex;
                sParamName = "ObjectID" + nParamIndex;
                cmd.Parameters.Add(sParamName, XPropType.vt_uuid, ParameterDirection.Input, false, oid);
                cmdBuilder.Append(context.Connection.GetParameterName(sParamName));
                cmdBuilder.Append(",");
            }
            // Отрежим последнюю запятую
            cmdBuilder.Length--;
            cmdBuilder.Append(")");
            cmd.CommandText = cmdBuilder.ToString();
            cmd.ExecuteNonQuery();

            // Если у переносимой папки были заданы направления, которые не соответствуют направлениям новой
            // родительской папки, то удалим эти направления
            if (request.NewParent != Guid.Empty)
            {
                cmdBuilder = new StringBuilder();


                cmd         = context.Connection.CreateCommand();
                nParamIndex = 0;
                foreach (Guid oid in request.ObjectsID)
                {
                    ++nParamIndex;
                    sParamName = "FolderID" + nParamIndex;
                    cmd.Parameters.Add(sParamName, XPropType.vt_uuid, ParameterDirection.Input, false, oid);
                    cmdBuilder.Append(context.Connection.GetParameterName(sParamName));
                    cmdBuilder.Append(",");
                }
                // Отрежим последнюю запятую
                cmdBuilder.Length--;
                cmd.CommandText = @"IF (EXISTS(
	                    SELECT top 1 fd.Direction
	                    FROM dbo.FolderDirection fd
	                    WHERE (fd.Direction not in 
	                        (
		                    SELECT Direction
		                    FROM dbo.FolderDirection
		                    WHERE  Folder = @ParentID) 
	                        AND fd.Folder IN ("     + cmdBuilder.ToString() + @"))  OR
                        EXISTS(
                            SELECT COUNT(*) 
                            FROM dbo.FolderDirection
                            WHERE Folder IN (" + cmdBuilder.ToString() + @")
                            HAVING COUNT(*)>1))) AND EXISTS( SELECT TOP 1 * FROM dbo.Folder WHERE ObjectID = @ParentID )
                                               
                        BEGIN
	                        DELETE fd
	                        FROM
	                        FolderDirection fd
	                        WHERE fd.Folder IN ("     + cmdBuilder.ToString() +
                                  @")
                        END"
                ;
                cmd.Parameters.Add("ParentID", DbType.Guid, ParameterDirection.Input, false, request.NewParent);
                cmd.ExecuteNonQuery();
            }

            return(new XResponse());
        }
예제 #6
0
        public XResponse Execute(SaveObjectInternalRequest request, IXExecutionContext context)
        {
            ITUser    user     = (ITUser)XSecurityManager.Instance.GetCurrentUser();
            ArrayList aObjects = request.DataSet.GetModifiedObjectsByType("TimeLoss", true);

            foreach (DomainObjectData xobj in aObjects)
            {
                if (!xobj.HasUpdatedProp("Worker") && xobj.IsNew)
                {
                    xobj.SetUpdatedPropValue("Worker", user.EmployeeID);
                }

                // получим папку, проверим у папки отсутствие флага IsLocked
                object           vValue     = xobj.GetPropValue("Folder", DomainObjectDataSetWalkingStrategies.UseUpdatedPropsThanLoadedProps);
                DomainObjectData xobjFolder = null;
                if (vValue is Guid)
                {
                    xobjFolder = xobj.Context.Get(context.Connection, xobj, "Folder", DomainObjectDataSetWalkingStrategies.UseUpdatedPropsThanLoadedProps, true);
                    // проверим состояние папки. если она закрыта или в ожидании закрытия, то запретим списание

                    if ((FolderStates)xobjFolder.GetLoadedPropValue("State") == FolderStates.Closed ||
                        (FolderStates)xobjFolder.GetLoadedPropValue("State") == FolderStates.WaitingToClose ||
                        (FolderStates)xobjFolder.GetLoadedPropValue("State") == FolderStates.Frozen)
                    {
                        throw new XSecurityException("Списания в папку в состоянии \"Закрыто\" или \"Ожидание закрытия\" запрещены. По вопросам списаний обращайтесь к менеджеру.");
                    }
                    // проверим наличие у папки специального аттрибута, запрещающего списание
                    if ((bool)xobjFolder.GetPropValue("IsLocked", DomainObjectDataSetWalkingStrategies.UseUpdatedPropsThanLoadedProps))
                    {
                        throw new XSecurityException("<b>Списания в данную папку запрещены.</b><br/>По вопросам списаний обращайтесь к менеджеру.");
                    }
                    FolderPrivilegeManager manager = (XSecurityManager.Instance.SecurityProvider as SecurityProvider).FolderPrivilegeManager;
                    // у пользователя должны быть права на списание в данной папке
                    if (!manager.HasFolderPrivilege(user, FolderPrivileges.SpentTimeByProject, xobjFolder, context.Connection))
                    {
                        throw new XSecurityException("<b>Пользователь должен обладать в проекте привилегией \"Списание времение на проект\".</b><br/>По вопросам списаний обращайтесь к менеджеру.");
                    }

                    // если у пользователя нет привилегии «Разрешение списания на папку с неоднозначным определением направления»
                    // проверим - является ли данная папка папкой с неоднозначным определением направления
                    if (!manager.HasFolderPrivilege(user, FolderPrivileges.TimeLossOnUnspecifiedDirection, xobjFolder, context.Connection))
                    {
                        using (XDbCommand c = context.Connection.CreateCommand())
                        {
                            c.CommandType = CommandType.Text;
                            c.CommandText = @"
IF	(
		SELECT t.AccountRelated 
		FROM dbo.ActivityType t WITH(NOLOCK) JOIN dbo.Folder fT WITH(NOLOCK) ON fT.ActivityType = t.ObjectID 
		WHERE fT.ObjectID = @FolderID
	) = 0
	SELECT 1
ELSE
	SELECT TOP 1 t.DirsQnt
	FROM (
		SELECT
			fU.ObjectID, fU.LRLevel,
			( SELECT COUNT(*) FROM dbo.FolderDirection fd WITH(NOLOCK) WHERE fd.Folder = fU.ObjectID ) AS DirsQnt
		FROM
			dbo.Folder fT WITH(NOLOCK)
			JOIN dbo.Folder fU WITH(NOLOCK) ON fU.Customer = fT.Customer AND fU.LIndex <= fT.LIndex AND fU.RIndex >= fT.RIndex
		WHERE
			fT.ObjectID = @FolderID
	) t
	WHERE DirsQnt > 0		
	ORDER BY LRLevel DESC 
";
                            c.Parameters.Add("FolderID", DbType.Guid, ParameterDirection.Input, false, vValue);

                            object oResult = c.ExecuteScalar();
                            string sReport = null;

                            if (null == oResult || DBNull.Value == oResult)
                            {
                                sReport = "для которой не определено ни одного направления";
                            }
                            else if (1 != (int)oResult)
                            {
                                sReport = "для которой определено более одного направления";
                            }

                            if (null != sReport)
                            {
                                throw new XSecurityException(
                                          "<b>Списания в данную папку запрещены.</b><br/>" +
                                          "У Вас нет прав списывать в папку, " + sReport + ". " +
                                          "По вопросам списаний <b>обращайтесь к менеджеру</b>.");
                            }
                        }
                    }
                }


                // если в объекте заданы "виртуальные" свойства
                if (xobj.HasUpdatedProp("LossFixedStart") && xobj.GetUpdatedPropValue("LossFixedStart") is DateTime &&
                    xobj.HasUpdatedProp("LossFixedEnd") && xobj.GetUpdatedPropValue("LossFixedEnd") is DateTime)
                {
                    if (!xobj.IsNew)
                    {
                        throw new ApplicationException("Задание диапазона допустимо только при создании объекта \"Списание времени\"");
                    }
                    // получим даты начала и окончания периода
                    DateTime dtPeriodStart = (DateTime)xobj.GetUpdatedPropValue("LossFixedStart");
                    DateTime dtPeriodEnd   = (DateTime)xobj.GetUpdatedPropValue("LossFixedEnd");
                    if (dtPeriodStart > dtPeriodEnd)
                    {
                        DateTime dtTemp = dtPeriodEnd;
                        dtPeriodEnd   = dtPeriodStart;
                        dtPeriodStart = dtTemp;
                    }

                    Guid employeeID = (Guid)xobj.GetUpdatedPropValue("Worker");
                    // проверим, что в данном периоде отсутствуют списания
                    XDbCommand cmd = context.Connection.CreateCommand("");
                    cmd.CommandText = @"SELECT DISTINCT CONVERT(varchar, x.SpentDate, 104) FROM (
						SELECT ts.RegDate AS SpentDate
						FROM TimeSpent ts
							JOIN Task t ON ts.Task = t.ObjectID
						WHERE t.Worker = @EmployeeID 
							AND ts.RegDate >= @dtPeriodStart AND ts.RegDate < @dtPeriodEnd
						UNION
						SELECT ts.LossFixed AS SpentDate
						FROM TimeLoss ts WHERE ts.Worker = @EmployeeID
							AND ts.LossFixed >= @dtPeriodStart AND ts.LossFixed < @dtPeriodEnd
						) x"                        ;
                    cmd.Parameters.Add("EmployeeID", DbType.Guid, ParameterDirection.Input, false, employeeID);
                    cmd.Parameters.Add("dtPeriodStart", DbType.Date, ParameterDirection.Input, false, dtPeriodStart);
                    // AddDays(1) - т.к. в условии запроса знак "меньше"
                    cmd.Parameters.Add("dtPeriodEnd", DbType.Date, ParameterDirection.Input, false, dtPeriodEnd.AddDays(1));
                    using (IDataReader reader = cmd.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            StringBuilder bld = new StringBuilder();
                            do
                            {
                                if (bld.Length > 0)
                                {
                                    bld.Append(", ");
                                }
                                bld.Append(reader.GetString(0));
                            } while(reader.Read());
                            throw new XBusinessLogicException(
                                      String.Format("У вас уже имеются списания на следующие даты в заданном периоде ({0},{1}): {2}",
                                                    dtPeriodStart.ToShortDateString(),
                                                    dtPeriodEnd.ToShortDateString(),
                                                    bld.ToString())
                                      );
                        }
                    }
                    // Получаем "набор пар" дата - норма сотрудника на дату
                    Dictionary <DateTime, int> dictDateRates = new Dictionary <DateTime, int>();
                    dictDateRates = GetDayRates(context.Connection, dtPeriodStart, dtPeriodEnd, employeeID);
                    // пойдем по всем дням периода (dtPeriodEnd, dtPeriodStart)
                    // и для каждой даты создадим новый объект Списание времени с количеством времени равном рабочему дню
                    // При этом, будем игнорировать даты, попадающие на выходные/праздники
                    TimeSpan period = (dtPeriodEnd - dtPeriodStart);
                    for (int nOffSet = 0; nOffSet <= period.TotalDays; ++nOffSet)
                    {
                        int      nRate  = 0;
                        DateTime dtDate = dtPeriodStart.AddDays(nOffSet);
                        // Если удалось получить норму сотрудника на соотв. дату, то создадим объект
                        if (dictDateRates.TryGetValue(dtDate, out nRate))
                        {
                            // Если полученная норма больше 0, тогда создадим списание
                            if (nRate > 0)
                            {
                                DomainObjectData xobjNew = createTimeLossObject(request.DataSet, xobj, user);
                                xobjNew.SetUpdatedPropValue("LossFixed", dtDate);
                                // списание длиной в количество минут в рабочем дне
                                xobjNew.SetUpdatedPropValue("LostTime", nRate);
                            }
                        }
                    }
                    // исходный объект надо удалить
                    request.DataSet.Remove(xobj);
                }
            }

            XSecurityManager sec_man    = XSecurityManager.Instance;
            IEnumerator      enumerator = request.DataSet.GetModifiedObjectsEnumerator(false);
            DomainObjectData xobject;

            while (enumerator.MoveNext())
            {
                xobject = (DomainObjectData)enumerator.Current;
                if (xobject.ToDelete)
                {
                    sec_man.DemandDeleteObjectPrivilege(xobject);
                }
                else
                {
                    sec_man.DemandSaveObjectPrivilege(xobject);
                }
            }
            // #1: Запись данных
            XStorageGateway.Save(context, request.DataSet, request.TransactionID);
            // Специального результата операция не возвращает
            return(new XResponse());
        }