📜 ⬆️ ⬇️

Creating project office tools based on Microsoft Project Server

Hello!

Today we will tell about our experience of using Project Server for planning and accounting of labor costs for projects, about how we quickly customized it for our tasks and achieved a clear picture as a result: managers see how the company works, how successfully projects are done, what is the efficiency of each individual employee for the requested time period, etc.

History and statistics of using Project Server in EastBanc Technologies

We have been using Project Server since 2005 to keep track of working time and work planning within the group of companies consisting of two offices in different time zones - in Russia and the USA. We also take into account in the system of temporarily involved contractors.
')
Sample statistics:

Total projects in the system - 603,

Employees - 216,

Time sheets of the accounting of working hours (they are the time sheets, they are the same timeshields) to check weekly - 140,

Tasks per week 260.

Workflow looks like this : we start each project in the Project, include all the project team members there, create a project plan (tasks, planned deadlines and labor costs). Employees regularly enter information about how much work time has been spent on project tasks for each working day - fill out the so-called time sheet, the time sheet.

For an employee, this simply looks like putting numbers on a table with tasks assigned to them by day. If necessary, the tasks in the project they can start independently. And since this is a “bureaucratic”, routine procedure for an employee that is easy to forget, automatic email reminders are set up.

Based on the data sent by employees, an OLAP-cube is built, which allows managers with a couple of clicks to construct reports of various formats - in fact, the report can be collected for any needs that occur to managers and analyze information in a convenient way from any angle.

Tools for implementing this solution on Project Server

What have we done to customize Project Server to our needs as described above?

There are several technical possibilities for constructing reports in sections of interest to us, which are provided by Project Server 2010:

1. Using one of the Project Server databases (how to configure here ).

In the end, a fairly simple query is made .

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 


The result using Pivot tables is published in Excel Services:



2. Using OLAP cubes embedded in Project Server (how to configure here ).

In Excel it looks like this:



List of fields available in the analytic cube:



In our case, we are faced with the fact that both methods have serious drawbacks.

In terms of data structure:

  1. You must be able to filter employees on the grounds of "fired" or "working", because For 10 years of the company's history, the Project Server user base has accumulated a fairly large archive.
  2. In the time dimension, you need to be able to build reports on real months, and not on fiscal periods, since they form the basis of time sheets for bookkeeping and are subsequently loaded with 1C.
  3. In an employee dimension, you need to understand the following things: a. Affiliation to a company division: Russia, America or external contracting organizations; b. Personnel number of the employee in 1C; c. E-mail address for sending notifications about a report that was not filled in on time.
  4. Project Objectives

The publication in Excel Services has one, but an important drawback: with a large amount of data (see the number of projects, employees, tasks in our system above) any use of the filter leads to the execution of a database query, and this is the time to wait for the results and build report.

OLAP cubes also have a nuance: they are divided into a series of different cubes with data scattered around them, for example, tasks in one and time sheets in another. In general, cubes are more focused on portfolio analysis than on ad hoc work.

What we have done to meet our needs:

We took the standard sql query from MicroSoft to Project Server as a basis for building an OLAP cube, slightly modifying it for our needs. In the frequency of made changes in time periods, because It is important for us to have two dimensions - by real weeks and by working weeks, we added an employee's e-mail box and the sign “fired”.

Code
 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) 

Due to the remoteness of the server, we had to make a small SSIS package to “transfer” the data to a temporary table. After that, we made the resulting table a data source for our cube, added the necessary measurements and measure.



To ensure up-to-date, sql-jobs were created for data acquisition, and then cube processing.



Practice has shown that employees tend to forget that they need to fill in reports on time spent weekly. To do this, we wrote a small SSIS package that performs an MDX request to the cube, identifies "forgetful" employees and sends a letter with a request to fill in the timesheet. Moreover, if today is Friday, then the current week is checked, and if Monday, Tuesday or Wednesday, then the past.



We will separately dwell on the problem that has arisen with this package. It consisted in the fact that the sql-job that executes this package "lives" in the GMT + 6 time zone. Our American colleagues need to send a reminder on their Friday at 5:00 pm, and in Novosibirsk it is already 5:00 am Saturday (or 4:00 am, depending on the transfer of hours to the USA), and since the work week in Cuba begins on Saturday, all colleagues a letter came from the United States that the report was not completed. The solution to this problem lies on the surface and consists of adding an additional condition to checking the current day of the week.

Result

Here are some examples of some reports:

1. Report for the period for all employees. Weekly, managers look through the sheets of all employees - they control the fact of filling in and the correctness of posting work on projects.





2. Project report. After the end of the project, managers can analyze how the implementation went: what tasks were actually done, how much resources were spent on them, how much this corresponds to our initial plans for the project. The same can be controlled in the course of the project.



3. Report on the employee. At any time, you can analyze the activities of a single project officer for the period of interest.



As a result:

  1. All EastBanc employees fill out at least 40 hours, since we have a 40-hour week. Fill on time!
  2. Employees fill in time protection boards correctly: holidays, sick-lists, holidays, projects on which they work, including internal ones, are spread out according to the necessary columns.
  3. Tasks on projects correspond to the plan, since they are quite detailed. There are no tasks for more than 16 hours to catch up on time when something goes wrong.
  4. Upon completion of each project, managers receive a detailed analysis.
  5. Each manager monitors their projects. The picture as a whole is being monitored by the project office manager.

Now we have a very convenient enterprise-tool that the whole company uses every day. It was possible to do everything by improving the existing standard Project Server tools for a very limited time, because There was a task, not deviating and not inventing a bicycle, to quickly solve the problem of accounting for labor costs.

Source: https://habr.com/ru/post/211222/


All Articles