3 февраля 2014 в 14:49

Создание инструментов проектного офиса на базе Microsoft Project Server

Привет!

Сегодня мы расскажем о своем опыте использования Project Server для планирования и учета трудозатрат по проектам, о том, как мы его оперативно настроили под свои задачи и добились в итоге четкой картины: менеджеры видят, как работает компания, насколько успешно сдаются проекты, какова эффективность каждого отдельно взятого сотрудника за запрашиваемый период времени и т.д.

История и статистика использования Project Server в EastBanc Technologies

Мы используем Project Server c 2005 года для учета рабочего времени и планирования работ в рамках группы компаний, состоящей из двух офисов в разных часовых поясах — в России и США. Также учитываем в системе временно привлекаемых подрядчиков.

Примерная статистика:

Всего проектов в системе — 603,

Сотрудников — 216,

Табелей учета рабочего времени (они же time sheets, они же таймщиты) на проверку еженедельно — 140,

Задач в неделю 260.

Workflow выглядит так: каждый проект мы заводим в Project, включаем туда всех членов проектной команды, создаем план проекта (задачи, планируемые сроки и трудозатраты). Сотрудники регулярно заносят информацию о том, сколько рабочего времени было потрачено на задачи по проектам за каждый рабочий день — заполняют так называемый time sheet, табель учета рабочего времени.

Для сотрудника это выглядит просто как проставление цифр в таблице с назначенными на них задачами по дням. При необходимости, задачи в проекте они могут заводить самостоятельно. А поскольку речь идет о «бюрократической», рутинной для сотрудника процедуре, о которой несложно забыть, настроены автоматические email-напоминания.

На основе данных, отправленных сотрудниками, строится OLAP-куб, который позволяет менеджерам парой кликов конструировать отчеты различных форматов – по сути, отчет можно собрать под любые нужды, которые приходят в голову менеджерам, и анализировать информацию в удобном виде в любом разрезе.

Инструменты для реализации данного решения на Project Server

Что мы сделали, чтобы настроить Project Server под свои нужды, описанные выше?

Для построения отчетности в интересующих нас разрезах существует несколько технических возможностей, которые предоставляет Project Server 2010:

1. Использование одной из баз данных Project Server (о том, как конфигурировать здесь).

В конечном счете делается довольно простой запрос.

SELECT DISTINCT EpmResource.ResourceTimesheetManagerUID, 
                MSP_EpmResource.ResourceName

FROM            MSP_EpmResource 
                INNER JOIN MSP_EpmResource AS EpmResource 
                   ON MSP_EpmResource.ResourceUID 
                      = EpmResource.ResourceTimesheetManagerUID 
                   AND EpmResource.ResourceUID 
                      <> EpmResource.ResourceTimesheetManagerUID

ORDER BY  


Результат с помощью Pivot таблиц публикуется в Excel Services:



2. Использование OLAP-кубов, встроенных в Project Server (как конфигурировать тут).

В Excel выглядит так:



Список полей, доступных в аналитическом кубе:



В нашем случае мы столкнулись с тем, что оба способа имеют серьезные недостатки.

С точки зрения структуры данных:

  1. Необходимо уметь фильтровать сотрудников по признакам «уволен» или «работает», т.к. за 10 лет истории компании база пользователей Project Server накопила довольно большой архив.
  2. В измерении времени нужно иметь возможность строить отчеты по реальным месяцам, а не по фискальным периодам, т.к. они ложатся в основу табелей учета рабочего времени для бухгалтерии и впоследствии загружаются 1С.
  3. В измерении сотрудника нужно понимать следующие вещи: a. Принадлежность к структурному подразделению компании: Россия, Америка или внешние организации-контрактеры; b. Табельный номер сотрудника в 1С; c. Адрес электронной почты для рассылки уведомлений о незаполненном вовремя отчете.
  4. Задачи проекта.

В публикации в Excel Services есть один, но важный недостаток: при большом объеме данных (см. количество проектов, сотрудников, задач в нашей системе выше) любое применение фильтра приводит к выполнению запроса на БД, а это — время на ожидание результатов и построение самого отчета.

У OLAP-кубов тоже есть нюанс: они разбиты на серию различных кубов с разбросанными по ним данными, например, задачи в одном, а time sheets в другом. В целом, кубы больше заточены на анализ портфеля, чем на Ad Hoc-работу.

Что мы сделали для обеспечения своих нужд:

За основу построения OLAP-куба мы взяли стандартный sql-запрос от MicroSoft’a к Project Server, немного доработав его под наши нужды. В частотности внесли изменения во временные периоды, т.к. нам важно иметь два измерения — по реальным неделям и по рабочим неделям, добавили электронный ящик сотрудника и признак «уволен».

Код
SELECT     R.StartDate, R.EndDate, R.PeriodName, R.TimeByDay, R.TimeByDay_DayOfWeek, R.ActualWorkBillable, R.ProjectName, 
                      R.TS_LINE_CACHED_ASSIGN_NAME + ' (' + R.ProjectName + ')' AS TaskName, R.Status, R.ProjectAccount, R.Location,  R.ResourceCompany, R.EmployeeID, R.TS_LINE_CLASS_NAME, R.Type, R.ProjectOwner, 
                      R.Firstname, R.LastName, R.ResourceName, R.ModifiedDate, R.ResourceNameUID, DATEPART(yyyy, R.EndDate) AS PeriodYear, DATEPART(mm, R.EndDate) 
                      AS PeriodMonth, DATEPART(ww, R.EndDate) AS PeriodWeek, DATEPART(yyyy, R.TimeByDay) AS RealPeriodYear, DATEPART(mm, R.TimeByDay) AS RealPeriodMonth, DATEPART(ww, R.TimeByDay) AS RealPeriodWeek, R.ProjectStatus, 
                      CASE WHEN ISNULL(R.RES_TERMINATION_DATE, GETDATE()) >= GETDATE() THEN 0 ELSE 1 END AS IsFire, R.WRES_EMAIL AS Email
FROM         (SELECT R_1.RES_HIRE_DATE, R_1.RES_TERMINATION_DATE, R_1.WRES_EMAIL, R_1.TODAY, R_1.IsActive, R_1.StartDate, R_1.EndDate, R_1.PeriodName, R_1.TimeByDay, 
                                              R_1.TimeByDay_DayOfWeek, R_1.ActualWorkBillable, R_1.ProjectName, R_1.[Task Name], R_1.Status, R_1.ProjectAccount, R_1.Location,  R_1.ResourceCompany,
                                              R_1.EmployeeID, R_1.TS_LINE_CACHED_ASSIGN_NAME, R_1.TS_LINE_CLASS_NAME, R_1.Type, R_1.TimesheetClass, R_1.ProjectOwner, R_1.Firstname, 
                                              R_1.LastName, R_1.ResourceName, R_1.ModifiedDate, R_1.ResourceNameUID, R_1.ResourceCC, R_1.CostCenter, R_1.ProjectType, CASE WHEN R_1.ProjectStatus IS NULL THEN 'Undefined' ELSE R_1.ProjectStatus END AS ProjectStatus, R_1.TS_LINE_UID, 
                                              DATEADD(day, - MIN(DATEDIFF(day, T.EFFECTIVE_DATE, R_1.TimeByDay)), R_1.TimeByDay) AS EFFECTIVE_DATE
                       FROM          (SELECT res.RES_HIRE_DATE, res.RES_TERMINATION_DATE, res.WRES_EMAIL, GETDATE() AS TODAY, CASE WHEN (res.RES_TERMINATION_DATE > GETDATE() OR
                                                                      res.RES_TERMINATION_DATE IS NULL) THEN 'Active' ELSE 'Inactive' END AS IsActive, tpr.WPRD_START_DATE AS StartDate, 
                                                                      tpr.WPRD_FINISH_DATE AS EndDate, tpr.WPRD_NAME AS PeriodName, ISNULL(tla.TS_ACT_START_DATE, tpr.WPRD_START_DATE) 
                                                                      AS TimeByDay, DATEPART(weekday, tla.TS_ACT_START_DATE) AS TimeByDay_DayOfWeek, tla.TS_ACT_VALUE / 60000 AS ActualWorkBillable, 
                                                                      CASE tcl.TS_LINE_CLASS_NAME WHEN 'Standard' THEN tp.PROJ_NAME ELSE tcl.TS_LINE_CLASS_NAME END AS ProjectName, 
                                                                      tsk.TASK_NAME AS [Task Name], 
                                                                      CASE t .TS_STATUS_ENUM WHEN 0 THEN 'InProgress' WHEN 1 THEN 'Submitted' WHEN 2 THEN 'Acceptable' WHEN 3 THEN 'Approved' WHEN
                                                                       4 THEN 'Rejected' WHEN 5 THEN 'Pending' ELSE 'Missing' END AS Status, 
                                                                      CASE tcl.TS_LINE_CLASS_NAME WHEN 'Standard' THEN PP.ProjectAccount WHEN 'Administrative & General' THEN '0700-000' WHEN 'Bench time'
                                                                       THEN '0702-000' WHEN 'Holidays' THEN '0500-000' WHEN 'Internal Projects' THEN '0701-000' WHEN 'Pre-sales & Overhead' THEN '0600-000' WHEN
                                                                       'Recruitment (interview)' THEN '0703-000' WHEN 'Sales activity' THEN '0704-000' WHEN 'Vacation' THEN '0209-000' ELSE PP.ProjectAccount END
                                                                       AS ProjectAccount, C.Location, RC.ResourceCompany, E.EmployeeID, tl.TS_LINE_CACHED_ASSIGN_NAME, tcl.TS_LINE_CLASS_NAME, 
                                                                      tcl.TS_LINE_CLASS_TYPE AS Type, tcltop.TS_LINE_CLASS_NAME AS TimesheetClass, pr_owner.RES_NAME AS ProjectOwner, 
                                                                      SUBSTRING(tr.RES_NAME, 0, CHARINDEX(' ', tr.RES_NAME)) AS Firstname, SUBSTRING(tr.RES_NAME, CHARINDEX(' ', tr.RES_NAME) + 1, 
                                                                      LEN(tr.RES_NAME)) AS LastName, SUBSTRING(tr.RES_NAME, CHARINDEX(' ', tr.RES_NAME) + 1, LEN(tr.RES_NAME)) 
                                                                      + ' ' + SUBSTRING(tr.RES_NAME, 0, CHARINDEX(' ', tr.RES_NAME)) AS ResourceName, t.MOD_DATE AS ModifiedDate, 
                                                                      tr.RES_UID AS ResourceNameUID, tr.ResourceCC, PCC.CostCenter, PT.ProjectType, PPS.ProjectStatus as ProjectStatus, tla.TS_LINE_UID
                                               FROM          pub.MSP_WEB_TIME_PERIODS AS tpr CROSS JOIN
                                                                          (SELECT     RES_UID, RES_NAME, CASE WHEN tr.ResourceCC = 4 OR
                                                                                                   tr.ResourceCC = 6 THEN 'Only DC' ELSE CASE WHEN tr.ResourceCC = 1 THEN 'Only NSK' ELSE 'DC & NSK' END END AS ResourceCC
                                                                            FROM          (SELECT     RES_UID, RES_NAME, SUM(CASE WHEN tr.CostCenter IS NULL 
                                                                                                                           THEN 4 ELSE CASE WHEN tr.CostCenter = 'DC' THEN 2 ELSE 1 END END) AS ResourceCC
                                                                                                    FROM          (SELECT DISTINCT tr.RES_UID, tr.RES_NAME, PCC.CostCenter
                                                                                                                            FROM          pub.MSP_RESOURCES AS tr INNER JOIN
                                                                                                                                                   pub.MSP_PROJECT_RESOURCES AS pr ON pr.RES_UID = tr.RES_UID INNER JOIN
                                                                                                                                                   pub.MSP_PROJECTS AS p ON p.PROJ_UID = pr.PROJ_UID LEFT OUTER JOIN
                                                                                                                                                       (SELECT     pspPrjCFV.PROJ_UID, psLV.LT_VALUE_TEXT AS CostCenter
                                                                                                                                                         FROM          pub.MSP_PROJ_CUSTOM_FIELD_VALUES AS pspPrjCFV INNER JOIN
                                                                                                                                                                                pub.MSP_CUSTOM_FIELDS AS pspCF ON 
                                                                                                                                                                                pspPrjCFV.MD_PROP_UID = pspCF.MD_PROP_UID LEFT OUTER JOIN
                                                                                                                                                                                pub.MSP_LOOKUP_TABLE_VALUES AS psLV ON 
                                                                                                                                                                                psLV.LT_STRUCT_UID = pspPrjCFV.CODE_VALUE
                                                                                                                                                         WHERE      (pspCF.MD_PROP_NAME = 'Cost_Center')) AS PCC ON PCC.PROJ_UID = p.PROJ_UID
                                                                                                                            WHERE      (tr.RES_TYPE = 2 OR tr.RES_TYPE = 102)) AS tr
                                                                                                    GROUP BY RES_UID, RES_NAME) AS tr) AS tr INNER JOIN
                                                                      pub.MSP_RESOURCES AS res ON res.RES_UID = tr.RES_UID LEFT OUTER JOIN
                                                                      pub.MSP_TIMESHEETS AS t ON t.WPRD_UID = tpr.WPRD_UID AND t.RES_UID = tr.RES_UID LEFT OUTER JOIN
                                                                      pub.MSP_TIMESHEET_LINES AS tl ON tl.TS_UID = t.TS_UID AND tl.TS_LINE_ACT_SUM_VALUE > 0 LEFT OUTER JOIN
                                                                      pub.MSP_TIMESHEET_ACTUALS AS tla ON tla.TS_LINE_UID = tl.TS_LINE_UID LEFT OUTER JOIN
                                                                      pub.MSP_TIMESHEET_CLASSES AS tcl ON tcl.TS_LINE_CLASS_UID = tl.TS_LINE_CLASS_UID LEFT OUTER JOIN
                                                                          (SELECT     TS_LINE_CLASS_UID, TS_LINE_CLASS_IS_EDITABLE, TS_LINE_CLASS_NAME, TS_LINE_CLASS_TYPE, 
                                                                                                   TS_LINE_CLASS_NEED_APPROVAL, TS_LINE_CLASS_ORGANIZATION, TS_LINE_CLASS_DESC, TS_LINE_CLASS_IS_DISABLED, 
                                                                                                   TS_LINE_CLASS_ALWAYS_DISPLAY, CREATED_DATE, MOD_DATE, CREATED_REV_COUNTER, MOD_REV_COUNTER
                                                                            FROM          pub.MSP_TIMESHEET_CLASSES
                                                                            WHERE      (TS_LINE_CLASS_TYPE = 0)) AS tcltop ON tcltop.TS_LINE_CLASS_UID = tl.TS_LINE_CLASS_UID LEFT OUTER JOIN
                                                                          (SELECT     PROJ_UID, PROJ_NAME, WRES_UID
                                                                            FROM          pub.MSP_PROJECTS
                                                                            UNION
                                                                            SELECT     'E38038FA-F8CA-47D1-BFD4-6B45B8462972' AS Expr1, 'Administrative' AS Expr2, NULL AS Expr3) AS tp ON 
                                                                      tp.PROJ_UID = tl.PROJ_UID LEFT OUTER JOIN
                                                                      pub.MSP_TASKS AS tsk ON tsk.TASK_UID = tl.TASK_UID LEFT OUTER JOIN
                                                                      pub.MSP_RESOURCES AS pr_owner ON pr_owner.RES_UID = tp.WRES_UID LEFT OUTER JOIN
                                                                          (SELECT     ppResCFV.RES_UID, ppResCFV.TEXT_VALUE AS EmployeeID
                                                                            FROM          pub.MSP_RES_CUSTOM_FIELD_VALUES AS ppResCFV INNER JOIN
                                                                                                   pub.MSP_CUSTOM_FIELDS AS ppCF ON ppResCFV.MD_PROP_UID = ppCF.MD_PROP_UID
                                                                            WHERE      (ppCF.MD_PROP_NAME = 'employeeID')) AS E ON tr.RES_UID = E.RES_UID LEFT OUTER JOIN
                                                                          (SELECT     ppResCFV.RES_UID, ppResCFV.TEXT_VALUE AS Location
                                                                            FROM          pub.MSP_RES_CUSTOM_FIELD_VALUES AS ppResCFV INNER JOIN
                                                                                                   pub.MSP_CUSTOM_FIELDS AS ppCF ON ppResCFV.MD_PROP_UID = ppCF.MD_PROP_UID
                                                                            WHERE      (ppCF.MD_PROP_NAME = 'co')) AS C ON tr.RES_UID = C.RES_UID LEFT OUTER JOIN
                                                                          (SELECT     pspPrjCFV.PROJ_UID, pspPrjCFV.TEXT_VALUE AS ProjectAccount
                                                                            FROM          pub.MSP_PROJ_CUSTOM_FIELD_VALUES AS pspPrjCFV INNER JOIN
                                                                                                   pub.MSP_CUSTOM_FIELDS AS pspCF ON pspPrjCFV.MD_PROP_UID = pspCF.MD_PROP_UID
                                                                            WHERE      (pspCF.MD_PROP_NAME = 'Project Account')) AS PP ON PP.PROJ_UID = tp.PROJ_UID LEFT OUTER JOIN
                                                                          (SELECT     pspPrjCFV.PROJ_UID, psLV.LT_VALUE_TEXT AS CostCenter
                                                                            FROM          pub.MSP_PROJ_CUSTOM_FIELD_VALUES AS pspPrjCFV INNER JOIN
                                                                                                   pub.MSP_CUSTOM_FIELDS AS pspCF ON pspPrjCFV.MD_PROP_UID = pspCF.MD_PROP_UID LEFT OUTER JOIN
                                                                                                   pub.MSP_LOOKUP_TABLE_VALUES AS psLV ON psLV.LT_STRUCT_UID = pspPrjCFV.CODE_VALUE
                                                                            WHERE      (pspCF.MD_PROP_NAME = 'Cost_Center')) AS PCC ON PCC.PROJ_UID = tp.PROJ_UID LEFT OUTER JOIN
                                                                          (SELECT     pspPrjCFV.PROJ_UID, psLV.LT_VALUE_TEXT AS ProjectStatus
                                                                            FROM          pub.MSP_PROJ_CUSTOM_FIELD_VALUES AS pspPrjCFV INNER JOIN
                                                                                                   pub.MSP_CUSTOM_FIELDS AS pspCF ON pspPrjCFV.MD_PROP_UID = pspCF.MD_PROP_UID LEFT OUTER JOIN
                                                                                                   pub.MSP_LOOKUP_TABLE_VALUES AS psLV ON psLV.LT_STRUCT_UID = pspPrjCFV.CODE_VALUE
                                                                            WHERE      (pspCF.MD_PROP_NAME = 'Project Status')) AS PPS ON PPS.PROJ_UID = tp.PROJ_UID LEFT OUTER JOIN                                                                            
                                                                          (SELECT     pspPrjCFV.PROJ_UID, psLV.LT_VALUE_TEXT AS ProjectType
                                                                            FROM          pub.MSP_PROJ_CUSTOM_FIELD_VALUES AS pspPrjCFV INNER JOIN
                                                                                                   pub.MSP_CUSTOM_FIELDS AS pspCF ON pspPrjCFV.MD_PROP_UID = pspCF.MD_PROP_UID LEFT OUTER JOIN
                                                                                                   pub.MSP_LOOKUP_TABLE_VALUES AS psLV ON psLV.LT_STRUCT_UID = pspPrjCFV.CODE_VALUE
                                                                            WHERE      (pspCF.MD_PROP_NAME = 'ProjectType')) AS PT ON PT.PROJ_UID = tp.PROJ_UID LEFT OUTER JOIN
                                                                          (SELECT     ppResCFV.RES_UID, ppResCFV.TEXT_VALUE AS ResourceCompany
                                                                            FROM          pub.MSP_RES_CUSTOM_FIELD_VALUES AS ppResCFV INNER JOIN
                                                                                                   pub.MSP_CUSTOM_FIELDS AS ppCF ON ppResCFV.MD_PROP_UID = ppCF.MD_PROP_UID
                                                                            WHERE      (ppCF.MD_PROP_NAME = 'Resource Company')) AS RC ON tr.RES_UID = RC.RES_UID
                                               WHERE      (tpr.WPRD_START_DATE < GETDATE()) AND (tpr.WPRD_START_DATE >= '12.01.2008')) AS R_1 LEFT OUTER JOIN
                                              CUSTOM_RES_PROJ_ASSIGNMENTS AS T ON T.RES_NAME = R_1.ResourceName AND T.PROJ_NAME = R_1.ProjectName AND 
                                              (T.EFFECTIVE_DATE IS NULL OR
                                              DATEDIFF(day, T.EFFECTIVE_DATE, R_1.TimeByDay) >= 0)
                       GROUP BY R_1.RES_HIRE_DATE, R_1.RES_TERMINATION_DATE, R_1.WRES_EMAIL, R_1.TODAY, R_1.IsActive, R_1.StartDate, R_1.EndDate, R_1.PeriodName, R_1.TimeByDay, 
                                              R_1.TimeByDay_DayOfWeek, R_1.ActualWorkBillable, R_1.ProjectName, R_1.[Task Name], R_1.Status, R_1.ProjectAccount, R_1.Location, R_1.ResourceCompany,
                                              R_1.EmployeeID, R_1.TS_LINE_CLASS_NAME, R_1.Type, R_1.TimesheetClass, R_1.ProjectOwner, R_1.Firstname, R_1.LastName, R_1.ResourceName, 
                                              R_1.ModifiedDate, R_1.ResourceNameUID, R_1.ResourceCC, R_1.CostCenter, R_1.ProjectStatus, R_1.ProjectType, R_1.TS_LINE_UID, 
                                              R_1.TS_LINE_CACHED_ASSIGN_NAME) AS R LEFT OUTER JOIN
                      CUSTOM_RES_PROJ_ASSIGNMENTS AS T ON T.RES_NAME = R.ResourceName AND T.PROJ_NAME = R.ProjectName AND (R.EFFECTIVE_DATE IS NOT NULL AND 
                      T.EFFECTIVE_DATE = R.EFFECTIVE_DATE OR
                      R.EFFECTIVE_DATE IS NULL AND T.EFFECTIVE_DATE IS NULL)

Из-за удалённости сервера нам пришлось сделать небольшой SSIS пакет для «перекачки» данных во временную таблицу. После этого мы сделали полученную таблицу источником данных для нашего куба, добавили необходимые измерения и меру.



Для обеспечения актуальности создали sql-джоб для получения данных, а затем и процессинга куба.



Практика показала — сотрудникам свойственно забывать, что отчеты о потраченном времени нужно заполнять еженедельно. Для это мы написали небольшой SSIS-пакет, который выполняет MDX-запрос к кубу, определяет «забывчивых» сотрудников и отправляет письмо с просьбой заполнить таймщит. При этом если сегодня пятница, то проверяется текущая неделя, а если понедельник, вторник или среда, то прошлая.



Отдельно остановимся на возникшей с данным пакетом проблемой. Она заключалась в том, что фактически sql-джоб, выполняющий данный пакет «живет» по часовому поясу GMT+6. Нашим американским коллегам необходимо отправлять напоминание в их пятницу в 17:00, а в Новосибирске это уже суббота 5:00 (либо 4:00 в зависимости от перевода часов в США), и так как рабочая неделя в кубе начинается с субботы, всем коллегам из США приходило письмо, что отчет не заполнен. Решение данной проблемы лежит на поверхности и заключается в добавлении дополнительного условия проверке текущего дня недели.

Результат

Вот что у нас получилось, примеры некоторых отчетов:

1. Отчет за период по всем сотрудникам. Еженедельно менеджеры просматривают табели всех сотрудников – контролируют сам факт заполнения и правильность разноски трудозатрат по проектам.





2. Отчет по проекту. После окончания проекта менеджеры могут проанализировать, как прошла реализация: какие задачи фактически были сделаны, сколько ресурсов на них потрачено, насколько это соответствует нашим изначальным планам на проект. Это же можно контролировать и по ходу проекта.



3. Отчет по сотруднику. В любой момент можно проанализировать деятельность отдельно взятого сотрудника по проектам за интересующий период.



В результате:

  1. Все сотрудники EastBanc заполняют минимум по 40 часов, так как у нас 40-часовая неделя. Заполняют вовремя!
  2. Сотрудники заполняют таймщиты правильно: отпуска, больничные, праздники, проекты, на которых они работают, в том числе и внутренние, — всё разносится по нужным графам.
  3. Задачи на проектах соответствуют плану, т.к. они достаточно детализированы. Нет задач больше 16 часов, чтобы вовремя спохватиться, когда что-то идет не так.
  4. По завершению каждого проекта менеджеры получают подробнейший анализ.
  5. Каждый менеджер следит за своими проектами. За картиной в целом следит менеджер проектного офиса.

Сейчас мы имеем очень удобный enterprise-инструмент, которым вся компания пользуется каждый день. Сделать все удалось с помощью улучшения существующих стандартных средств Project Server за очень ограниченное время, т.к. стояла задача, не изощряясь и не изобретая велосипед, быстро решить проблему учета трудозатрат.
Автор: @eastbanctech

Комментарии (5)

  • +1
    В общем, понятно. Начало положено. Вопрос только в полезности и соответствии данных по трудозатратам реальности…

    По технической стороне замечание такое — чтобы не попадали «старые» сотрудники", просто приделайте фильтр по дате.

    Более общие вопросы — организация чем занимается? Кто графики составляет, как?
    • 0
      Фильтр по дате и так есть, как по фискальной, так и по реальным месяцам. Для старых сотрудников мы используем признак уволен он или нет, который стал свойством измерения аналитического куба — что более правильно: ставишь в одном месте флажок (в стандартных свойствах пользователя Project Server) и не нужно думать, за какую тебе дату отфильтровать, чтобы лишних данных не было.

      Организация занимается разработкой программного обеспечения на заказ, планы работ составляют руководители проектов.

      Полезность есть, так как это инструмент оперативного контроля, а спустя 3 — 4 месяца от старта проекта без оперативного контроля может получиться такой бардак в учете, что они будут уже не пригодны для анализа, т.е. это вопрос чистоты данных, а значит правильности принятия решений.
  • +1
    Не совсем понимаю… Если есть дата, за которую работал уже уволенный сотрудник — почему не выводить эти данные?

    Эхехе… Там, поди-ка, и бюджет завязан, да?.. Процент исполнения задач, то да се… Бойтесь принятия решений на этой основе… Впрочем, этот вопрос не к программистам. Мы, знаете ли, реальные данные пособирали тут, правда, не в программерской конторе, а в проектной. Грустно, честно говоря…
    • 0
      Признак уволен нужен потому, что Project Server так устроен, что даже если сотрудник уволен, то у него в таймщите в базе будут пустые строки, т.е. за какую бы дату не смотрел данные все равно будут. Нужно четко знать — он не заполнил или его уже нет и он не должен заполнять.
      • 0
        Так вы же временную базу генерируете? Вот там и убирать лишнее.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разное