Как стать автором
Обновить
0
True Engineering
Лаборатория технологических инноваций

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

Время на прочтение10 мин
Количество просмотров12K
Привет!

Сегодня мы расскажем о своем опыте использования 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 за очень ограниченное время, т.к. стояла задача, не изощряясь и не изобретая велосипед, быстро решить проблему учета трудозатрат.
Теги:
Хабы:
Всего голосов 11: ↑6 и ↓5+1
Комментарии5

Публикации

Информация

Сайт
www.trueengineering.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия

Истории