Project Server 计划工时不更新

Project Server Planned Work Does not update

我正在开发 Project Server 2010 时间表导入程序。 它与 Project Web App(他们导入)中的实际时间配合得很好。

问题

问题是客户需要根据输入的实际工时更新计划工时。
调用 timesheetClient.QueueUpdateTimesheet() 后,它不会根据 Project Server 自动工作计划功能更新计划工作:<

如果您查看屏幕截图,则有 22 小时和 10 小时,但没有 "Zaplanowana" -> 计划值!

如果您只需单击文本并手动输入它们,就会出现计划的工作。

我可以手动更改timesheetDs.TS_ACT_PLAN_VALUE,但我不想手动编写复杂的工作计划算法(相信我,在 Project 世界中它真的很复杂)。

actual.TS_ACT_VALUE = 1000 * 60 * hours;
actual.TS_ACT_PLAN_VALUE = ???;

两种可能的解决方案

1。破解 PWA 时间表 JSGrid

(Javascript controll) 通过尝试模拟用户手动编辑行,这是 "quite" 困难

window.timesheetComponent.get_TimesheetSatellite().GetJsGridControlInstance()

2。尝试使用 PWA 时间表控件使用的相同网络服务

问题是它没有记录。
我们可以(理论上)在 /pwa/_vti_bin/PSI/ProjectServer.svc 上调用 TimeSheetSendGridUpdatesForSave 操作,下一次调用以检索小时数(通过官方 TimesheetClient 读取时间表)或 TimeSheetGetTimesheetForGridJsonFromViewUid 作为 pwa js 客户端调用,将导致更新的计划小时数

该方法有一个我还没有解决的问题 - 如何进行身份验证 - 关于第二个问题的更多信息

关键词:计划工作、计划时间、pwa 计划、项目服务器 2010、planowane godziny

免责声明优先

使用风险自负。此代码不可移植,这意味着 Microsoft 没有义务通知您其内部网络服务中发生了某些更改。

工作解决方案

尝试使用与 PWA 时间表控件相同的网络服务 问题是它没有记录。 我们可以在 /pwa/_vti_bin/PSI/ProjectServer.svc.

上调用 TimeSheetSendGridUpdatesForSave 操作

如何?

首先,您必须在标准 dll 之外针对 PSI 进行身份验证。 我自己描述和解决的问题


然后你需要用数据调用webservice。 例如,我们将使用此请求,它将我任务的实际小时数更改为 12 小时。以下代码段是从 Chrome DevTools -> 'Network' 选项卡复制的,当您更改时间并单击实际 PWA 时间表客户端上的保存(在浏览器中 http://servername/pwa/Timesheet.aspx?tsUID=06b92bf0-806e-44d5-8c94-616c50471920

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <TimeSheetSendGridUpdatesForSave
            xmlns="http://schemas.microsoft.com/office/project/server/webservices/PWA/">
            <jobUid>{ba875aee-a59f-4c8f-974e-cd5f21dff7b6}</jobUid>
            <tsUid>{06b92bf0-806e-44d5-8c94-616c50471920}</tsUid>
            <changesJson>[
  {
    "updates": [
      {
        "type": 2,
        "recordKey": "a81cc5f7-307a-46ce-a131-77e15468c29f",
        "fieldKey": "TPD_col2a",
        "newProp": { "dataValue": "720000", "hasDataValue": true }
      },
      {
        "type": 2,
        "recordKey": "a81cc5f7-307a-46ce-a131-77e15468c29f",
        "fieldKey": "TPD_col2t",
        "newProp": { "dataValue": "720000", "hasDataValue": true }
      }
    ],
    "changeNumber": 20
  }
]
</changesJson>
            <viewOptionsJson>{"dateFormat":3,"workFormat":2,"durationFormat":7,"filterType":5,"loadViewProperties":false,"newTasks":[],"importTasks":[],"removedLines":[]}</viewOptionsJson>
        </TimeSheetSendGridUpdatesForSave>
    </soap:Body>
</soap:Envelope>

在 SOAP UI 的 Auth 选项卡中,您必须提供凭据 作为拥有时间表的用户 。您不能只从管理员帐户发出请求,因为 PSI 会 return

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <s:Fault>
         <faultcode>s:Server</faultcode>
         <faultstring xml:lang="pl-PL">ProjectServerError(s) LastError=GeneralInvalidOperation Instructions: Pass this into PSClientError constructor to access all error information</faultstring>
         <detail>
            <errinfo>
               <general>
                  <class name="Nie można wprowadzić zmian w grafiku. Grafik nie został przesłany lub został odwołany.">
                     <error id="20011" name="GeneralInvalidOperation" uid="36ec16f4-db82-4fee-8922-2cd8d4084829"/>
                  </class>
               </general>
            </errinfo>
         </detail>
      </s:Fault>
   </s:Body>
</s:Envelope>

就像下面的 gif 一样

2019 年 8 月 27 日更新

呼叫代码:

[TestMethod]
        public void SampleChangeHoursInPlainDotNetWebClient()
        {
            var httpClient = new WebClient();
            httpClient.UseDefaultCredentials = true; // you have to be in a timesheets user scope (windows auth, ntlm)
            httpClient.Headers.Add("SOAPAction", "http://schemas.microsoft.com/office/project/server/webservices/PWA/TimeSheetSendGridUpdatesForSave");
            httpClient.Headers.Add("Content-Type", "text/xml; charset=UTF-8");
            httpClient.Headers.Add("Accept-Language", "en-US,en;q=0.9,pl;q=0.8");
            httpClient.Headers.Add("Accept-Encoding", "gzip, deflate");
            httpClient.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
            httpClient.Headers.Add("AsmxRoutedCall", "true");
            httpClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36");
            var data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><TimeSheetSendGridUpdatesForSave xmlns=\"http://schemas.microsoft.com/office/project/server/webservices/PWA/\"><jobUid>{ad8bd458-564c-49c5-8955-dfa577556d4a}</jobUid><tsUid>{6856cac1-7c88-4020-bdb5-c4e1b1a54b85}</tsUid><changesJson>[{\"updates\":[{\"type\":2,\"recordKey\":\"f88b5297-1985-4c2c-8717-554167471e81\",\"fieldKey\":\"TPD_col0a\",\"newProp\":{\"dataValue\":\"300000\",\"hasDataValue\":true}},{\"type\":2,\"recordKey\":\"f88b5297-1985-4c2c-8717-554167471e81\",\"fieldKey\":\"TS_LINE_STATUS\",\"newProp\":{\"dataValue\":\"0\",\"hasDataValue\":true,\"localizedValue\":\"Nieprzes%u0142ane\",\"hasLocalizedValue\":true}},{\"type\":2,\"recordKey\":\"f88b5297-1985-4c2c-8717-554167471e81\",\"fieldKey\":\"TPD_col0t\",\"newProp\":{\"dataValue\":\"300000\",\"hasDataValue\":true}}],\"changeNumber\":2},]</changesJson><viewOptionsJson>{\"dateFormat\":3,\"workFormat\":2,\"durationFormat\":7,\"filterType\":5,\"loadViewProperties\":true,\"newTasks\":[],\"importTasks\":[],\"removedLines\":[]}</viewOptionsJson></TimeSheetSendGridUpdatesForSave></soap:Body></soap:Envelope>";
            var response = httpClient.UploadString("http://preprod2010/pwa/_vti_bin/PSI/ProjectServer.svc", "POST", data);
        }

使用 RestSharp:

[TestMethod]
public void SampleChangeHours()
{
    var client = new RestClient("http://preprod2010/pwa/_vti_bin/PSI/ProjectServer.svc");
    var request = new RestRequest(Method.POST);
    request.AddHeader("cache-control", "no-cache");
    client.Authenticator = new NtlmAuthenticator(new NetworkCredential("test1","Start123!@#","CORPNET"));
    request.AddHeader("Host", "preprod2010");
    request.AddHeader("SOAPAction", "http://schemas.microsoft.com/office/project/server/webservices/PWA/TimeSheetSendGridUpdatesForSave");
    request.AddHeader("Connection", "keep-alive");

    request.AddHeader("Content-Type", "text/xml; charset=UTF-8");
    request.AddHeader("Accept-Language", "en-US,en;q=0.9,pl;q=0.8");
    request.AddHeader("Accept-Encoding", "gzip, deflate");
    request.AddHeader("X-FORMS_BASED_AUTH_ACCEPTED", "f");
    request.AddHeader("AsmxRoutedCall", "true");
    request.AddHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36");
    request.AddParameter("undefined", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><TimeSheetSendGridUpdatesForSave xmlns=\"http://schemas.microsoft.com/office/project/server/webservices/PWA/\"><jobUid>{ad8bd458-564c-49c5-8955-dfa577556d4a}</jobUid><tsUid>{6856cac1-7c88-4020-bdb5-c4e1b1a54b85}</tsUid><changesJson>[{\"updates\":[{\"type\":2,\"recordKey\":\"f88b5297-1985-4c2c-8717-554167471e81\",\"fieldKey\":\"TPD_col0a\",\"newProp\":{\"dataValue\":\"300000\",\"hasDataValue\":true}},{\"type\":2,\"recordKey\":\"f88b5297-1985-4c2c-8717-554167471e81\",\"fieldKey\":\"TS_LINE_STATUS\",\"newProp\":{\"dataValue\":\"0\",\"hasDataValue\":true,\"localizedValue\":\"Nieprzes%u0142ane\",\"hasLocalizedValue\":true}},{\"type\":2,\"recordKey\":\"f88b5297-1985-4c2c-8717-554167471e81\",\"fieldKey\":\"TPD_col0t\",\"newProp\":{\"dataValue\":\"300000\",\"hasDataValue\":true}}],\"changeNumber\":2},]</changesJson><viewOptionsJson>{\"dateFormat\":3,\"workFormat\":2,\"durationFormat\":7,\"filterType\":5,\"loadViewProperties\":true,\"newTasks\":[],\"importTasks\":[],\"removedLines\":[]}</viewOptionsJson></TimeSheetSendGridUpdatesForSave></soap:Body></soap:Envelope>", ParameterType.RequestBody);
    IRestResponse response = client.Execute(request);
}