Views: 22
這篇要教怎麼做外部起單

整體的概念上如上圖
一等一BPM的幾種起單方式
- 資料庫起單
 - 程式起單(本範例用這個)
 - UOF Web Service起單
 
資料庫起單
UOF 2的排程加入
<trigger file="Ede.Uof.WKF" type="Ede.Uof.WKF.CreateTaskByExternalDt" mode="EveryDay" hours="*" minutes="0/5" />
指定附件路徑,在Web.Config的<appSetting>加入路徑
<add key="wkfFileTransferTemp" value="C:\某資料夾\"/>
將表單的資料Insert到TB_WKF_EXTERNAL_TASK這個資料表 
WebService起單
呼叫站台的網址 /PublicAPI/WKF/WKF.asmx
方法為 SendForm(string token,string formInfo)
token放WebService取得的token,formInfo放表單資料
這個做法缺點是要透過另一個WebService先取得Token,然後取得這個Token最大的麻煩是需要使用者EIP的某個人帳密,我不使用這個方法。
程式起單
var task = new Ede.Uof.WKF.Utility.TaskUtilityUCO();
var task_result = task.WebService_CreateTask("...表單的XML");
在Ede.Uof.WKF.dll內有個函數可以直接起單
這邊只要輸入表單的XML資料即可完成起單,這個做法在開發上比較好做。
開發思路
第一個要決定的是在哪邊起單,這個場景是在ERP上起單,然後在BPM上簽核,在將簽核結果回傳到ERP中(這個案例不處理這段),開發的
- 先在EIP上設計表單
 - 發佈表單
 - 從資料庫取得表單範例XML
 - 寫一個Web Servicer接收資料後,組出XML呼叫
Ede.Uof.WKF.Utility.TaskUtility.WebService_CreateTask(string formInfo) 
設計一個簡單表單
這邊就請依照實際需求調整,
表單名稱:測試外部起單
- 表單編號
DOC_NBR - ERP單號 
ERP_NBR單行文字 - 申請日期
CREATE_DATE日期欄位 - 主旨
SUBJECT單行文字 
簽核流程設定
一開始設定時只要設定一站申請者自己簽就好,上線
表單標題
這邊請依照需求自行設定
發佈表單
設計的差不多就勇敢地把表單發佈出去
表單的設定
不要勾[下一站若為相同簽核者則跳過]
寫Web Service
這邊在Visual Studio中開啟站台的專案,然後在CDS下新增一個WS的資料夾,作為等下寫WebService用
然後我們要先在WS下新增一個Web.config檔案
對剛新增的WS資料夾按右鍵>Add 新增 > Add New Item 新增項目
選Web Configuration file (我記得翻譯成 Web組態設定檔),選到之後副檔名會是Web.config,然後Add 新增
接著新增內容如下WKF.asmx
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="WKF.asmx">
    <system.web>
      <authorization>
        <allow users="*"/>
      </authorization>
    </system.web>
  </location>
</configuration>
內容這邊就是location path="WKF.asmx" 這裡的WKF.asmx是等下要寫得WebService的檔名
接著我們要新增WebService的程式,這邊同樣Add > Add New Item
- 選WebService(ASMX) -> Visual C#
 - 命名為
WKF.asmx這邊就看你想取名為什麼 - 不要勾 Place code in seoarate file (這點重要)
 - Add
 
接著測試站台
先測試這個頁面有沒有東西 在網址輸入 站台網址/CDS/WS/WKF.asmx
如果沒出現通常是web.config有問題,或者網址路徑有問題
取得表單的XML
先申請一張單,然後按下儲存
接著到資料庫透過SQL查詢草稿內容
SELECT TOP (1) [CONTENT]   FROM [dbo].[TB_WKF_SCRIPT]  ORDER BY MODIFY_TIME DESC
CONTENT點進去後如上圖,這是剛剛儲存的表單資料,這邊的資料我們先複製到程式當中
我在WKF當中增加了一個Start(),先將XML結構放進去,這邊的XML還需要補上起單者的資料,一等一給的範例如下

主要是這個起單者資料要加入
  <Applicant account="UOF的帳號" groupId="" jobTitleId="">
    <Comment>申請者意見</Comment>
    <Attach IsNeedTransfer="true" IsDeleteTemp="false">
      <AttachItem filePath="資料庫路徑" />
    </Attach>
  </Applicant>
把這段也加入程式碼的註解當中,然後我把程式碼的hello給刪除了,Start加上WebMethod這個Filter
<%@ WebService Language="C#" Class="WKF" %>
using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
// [System.Web.Script.Services.ScriptService]
public class WKF  : System.Web.Services.WebService {
    [WebMethod(Description = "ERP起單")]
    public string Start()
    {
        /*XML結構
            <Form formVersionId="48a6bd19-2c73-4691-9548-baa5de18876a" urgentLevel="2">
               <Applicant account="UOF的帳號" groupId="" jobTitleId="">
                 <Comment>ERP起單</Comment>
               </Applicant>
               <FormFieldValue>
                <FieldItem fieldId="DOC_NBR" fieldValue="" realValue="" enableSearch="True" />
                <FieldItem fieldId="CREATE_DATE" fieldValue="2025/10/14" realValue="" enableSearch="True" fillerName="WISH" fillerUserGuid="084ecfd9-b65a-4b99-b649-d4abcf03112c" fillerAccount="wish" fillSiteId="" />
                <FieldItem fieldId="ERP_NBR" fieldValue="123" realValue="" enableSearch="True" fillerName="WISH" fillerUserGuid="084ecfd9-b65a-4b99-b649-d4abcf03112c" fillerAccount="wish" fillSiteId="" />
                <FieldItem fieldId="SUBJECT" fieldValue="aaaaabbbccc" realValue="" enableSearch="True" fillerName="WISH" fillerUserGuid="084ecfd9-b65a-4b99-b649-d4abcf03112c" fillerAccount="wish" fillSiteId="" />
              </FormFieldValue>
           </Form>
         */
        
        return "";
    }
}
注意要加入 urgentLevel
這邊還有個坑是要加上緊急程度

開始寫起單
Start的輸入我們加入從ERP來的表單欄位
這邊的重點是有個ACCOUNT欄位,作為起單用途
如果欄位很多或者有明細表或檔案,我會將資料模型寫在dll,通常在這張表單的Trigger內
加入XML的特殊字元轉換
using System.Security;
/// <summary>
/// 處理特殊字串轉換
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
static string XmlEscape(string s) => SecurityElement.Escape(s) ?? string.Empty;
將傳送來的內容組合成字串
[WebMethod(Description = "ERP起單")]
public string Start(string CREATE_DATE,string ERP_NBR,string SUBJECT,string ACCOUNT)
{
    var docXml = $@"
        <Form formVersionId=""48a6bd19-2c73-4691-9548-baa5de18876a"">
           <Applicant account=""{XmlEscape(ACCOUNT)}"" groupId="""" jobTitleId="""">
             <Comment>ERP起單</Comment>
           </Applicant>
           <FormFieldValue>
            <FieldItem fieldId=""DOC_NBR"" fieldValue="""" realValue="""" enableSearch=""True"" />
            <FieldItem fieldId=""CREATE_DATE"" fieldValue=""{XmlEscape(CREATE_DATE)}"" realValue="""" enableSearch=""True"" fillerName="""" fillerUserGuid="""" fillerAccount="""" fillSiteId="""" />
            <FieldItem fieldId=""ERP_NBR"" fieldValue=""{XmlEscape(ERP_NBR)}"" realValue="""" enableSearch=""True"" fillerName="""" fillerUserGuid="""" fillerAccount="""" fillSiteId="""" />
            <FieldItem fieldId=""SUBJECT"" fieldValue=""{XmlEscape(SUBJECT)}"" realValue="""" enableSearch=""True"" fillerName="""" fillerUserGuid="""" fillerAccount="""" fillSiteId="""" />
          </FormFieldValue>
       </Form>
";
        
    var task = new Ede.Uof.WKF.Utility.TaskUtilityUCO();
    var task_result = task.WebService_CreateTask(docXml);
    return task_result;
}
這邊我做了幾件事
- 新增
docXml這個字串變數var docXml = $@"表單的xml內容.."; - 把原本註解的
xml貼進去docXml當中 - 把
fillerName跟fillerUserGuid跟fillerAccount的內容都清掉 - 把起單的帳號跟欄位的
fieldValue換成對應的變數,這邊的寫法是{XmlEscape(變數)} - 加上起單帳號
 
測試起單資料
同樣透過瀏覽器瀏覽WebService的網址,然後寫剛剛寫好的Start
填寫資料 > 叫用
測試成功
檢視回傳結果
成功訊息
<?xml version="1.0" encoding="utf-8"?>
  <ReturnValue>
  <Status>1</Status>
  <FormNumber>表單編號</FormNumber>
  <TaskId>某ID</TaskId>
</ReturnValue>
失敗訊息
<?xml version="1.0" encoding="utf-8"?> 
<ReturnValue> 
<Status>0</Status> 
<Exception> 
  <Type>錯誤類型</Type> 
  <Message>錯誤訊息</Message> 
</Exception> 
</ReturnValue>
填入正確的使用者資訊
雖然現在做法可以正確起單,但使用者資訊是錯的,如果需要退回申請者修改之類的會出現問題,這邊加入先查詢使用者帳號,查詢後填入對應位置。
fillerName填入{user.Name}fillerUserGuid填入{user.UserGUID}fillerAccount填入{user.Account}
[WebMethod(Description = "ERP起單")]
public string Start(string CREATE_DATE,string ERP_NBR,string SUBJECT,string ACCOUNT)
{
    //查詢使用者資料
    var userUco = new UserUCO();
    var userGuid = userUco.GetGUID(ACCOUNT);
    var user = userUco.GetEBUser(userGuid);
    var docXml = $@"
        <Form formVersionId=""48a6bd19-2c73-4691-9548-baa5de18876a"" urgentLevel=""2"">
           <Applicant account=""{XmlEscape(user.Account)}"" groupId="""" jobTitleId="""">
             <Comment>ERP起單</Comment>
           </Applicant>
           <FormFieldValue>
            <FieldItem fieldId=""DOC_NBR"" fieldValue="""" realValue="""" enableSearch=""True"" />
            <FieldItem fieldId=""CREATE_DATE"" fieldValue=""{XmlEscape(CREATE_DATE)}"" realValue="""" enableSearch=""True"" fillerName=""{XmlEscape(user.Name)}"" fillerUserGuid=""{XmlEscape(user.UserGUID)}"" fillerAccount=""{XmlEscape(user.Account)}"" fillSiteId="""" />
            <FieldItem fieldId=""ERP_NBR"" fieldValue=""{XmlEscape(ERP_NBR)}"" realValue="""" enableSearch=""True"" fillerName=""{XmlEscape(user.Name)}"" fillerUserGuid=""{XmlEscape(user.UserGUID)}"" fillerAccount=""{XmlEscape(user.Account)}"" fillSiteId="""" />
            <FieldItem fieldId=""SUBJECT"" fieldValue=""{XmlEscape(SUBJECT)}"" realValue="""" enableSearch=""True"" fillerName=""{XmlEscape(user.Name)}"" fillerUserGuid=""{XmlEscape(user.UserGUID)}"" fillerAccount=""{XmlEscape(user.Account)}"" fillSiteId="""" />
          </FormFieldValue>
       </Form>
";
    //起單
    var task = new Ede.Uof.WKF.Utility.TaskUtilityUCO();
    var task_result = task.WebService_CreateTask(docXml);
    return task_result;
}
完整程式碼 WKF.asmx
<%@ WebService Language="C#" Class="WKF" %>
using System;
using System.Data.SqlClient;
using System.Security;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using Dapper;
using Ede.Uof.EIP.Organization.Util;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
// [System.Web.Script.Services.ScriptService]
public class WKF  : System.Web.Services.WebService {
    [WebMethod(Description = "ERP起單")]
    public string Start(string CREATE_DATE,string ERP_NBR,string SUBJECT,string ACCOUNT)
    {
        //查詢使用者資料
        var userUco = new UserUCO();
        var userGuid = userUco.GetGUID(ACCOUNT);
        var user = userUco.GetEBUser(userGuid);
        //組合SQL資料
        var docXml = $@"
            <Form formVersionId=""48a6bd19-2c73-4691-9548-baa5de18876a"" urgentLevel=""2"">
               <Applicant account=""{XmlEscape(user.Account)}"" groupId="""" jobTitleId="""">
                 <Comment>ERP起單</Comment>
               </Applicant>
               <FormFieldValue>
                <FieldItem fieldId=""DOC_NBR"" fieldValue="""" realValue="""" enableSearch=""True"" />
                <FieldItem fieldId=""CREATE_DATE"" fieldValue=""{XmlEscape(CREATE_DATE)}"" realValue="""" enableSearch=""True"" fillerName=""{XmlEscape(user.Name)}"" fillerUserGuid=""{XmlEscape(user.UserGUID)}"" fillerAccount=""{XmlEscape(user.Account)}"" fillSiteId="""" />
                <FieldItem fieldId=""ERP_NBR"" fieldValue=""{XmlEscape(ERP_NBR)}"" realValue="""" enableSearch=""True"" fillerName=""{XmlEscape(user.Name)}"" fillerUserGuid=""{XmlEscape(user.UserGUID)}"" fillerAccount=""{XmlEscape(user.Account)}"" fillSiteId="""" />
                <FieldItem fieldId=""SUBJECT"" fieldValue=""{XmlEscape(SUBJECT)}"" realValue="""" enableSearch=""True"" fillerName=""{XmlEscape(user.Name)}"" fillerUserGuid=""{XmlEscape(user.UserGUID)}"" fillerAccount=""{XmlEscape(user.Account)}"" fillSiteId="""" />
              </FormFieldValue>
           </Form>
";
        //起單
        var task = new Ede.Uof.WKF.Utility.TaskUtilityUCO();
        var task_result = task.WebService_CreateTask(docXml);
        return task_result;
    }
    /// <summary>
    /// XML處理特殊字串轉換
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    static string XmlEscape(string s) => SecurityElement.Escape(s) ?? string.Empty;
}
補充 – 更優雅的 XML fieldValue 欄位修改
可以用這段程式碼,我請ChatGPT寫的
/// <summary>
/// 設定指定 fieldId 的 fieldValue 屬性值,若不存在則新增。
/// </summary>
/// <param name="docXml">要處理的 XmlDocument。</param>
/// <param name="fieldId">欄位的 fieldId。</param>
/// <param name="value">要設定的fieldValue值。</param>
private void SetFieldValue(System.Xml.XmlDocument docXml, string fieldId, string value)
{
  if (docXml == null) 
    throw new ArgumentNullException(nameof(docXml));
  if (string.IsNullOrWhiteSpace(fieldId))
    throw new ArgumentException("fieldId 不可為空白", nameof(fieldId));
  var xpath = $"//FormFieldValue/FieldItem[@fieldId='{fieldId}']";
  var node = docXml.SelectSingleNode(xpath) as System.Xml.XmlElement;
  if (node == null)
    throw new InvalidOperationException($"找不到 FieldItem fieldId='{fieldId}'。");
  // 取得或建立 fieldValue 屬性
  var attr = node.GetAttributeNode("fieldValue") ?? docXml.CreateAttribute("fieldValue");
  attr.Value = value ?? string.Empty;
  // 如果是新建屬性記得加回去
  if (attr.OwnerElement == null)
      node.Attributes.Append(attr);
}
使用方式
SetFieldValue(docXml, "ERP_NBR", ERP_NBR);
補充-從資料庫取得表單版本ID
提供使用Dapper作為ORM的寫法,如果需要ADO.NET的版本可以請AI改寫
/// <summary>
/// 資料庫連線字串
/// </summary>
private readonly string _connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["connectionstring"].ConnectionString;
/// <summary>
/// 取得表單正在使用的Version ID
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
private string GetVersionID(string name)
{
    name = $"%{name}%";
    string sql = @"
      SELECT TOP (1)
        [USING_VERSION_ID]
      FROM [TB_WKF_FORM]  
      Where FORM_NAME like @name";
    using (var conn = new SqlConnection(_connectionString))
    {
        conn.Open();
        return conn.ExecuteScalar<string>(sql, new{name});
    }
}
補充-透過Email查詢為eip帳號
/// <summary>
/// Email 查詢EIP的Account (DAPPER版)
/// </summary>
/// <param name="mail">使用者信箱</param>
/// <returns>使用者Account </returns>
private string GetAccount(string mail)
{
    mail = mail?.Trim();
    string sql = @"SELECT TOP 1 [Account] FROM [dbo].[TB_EB_USER] nolock  where Email = @mail";
    using (var conn = new SqlConnection(_connectionString))
    {
        return conn.ExecuteScalar<string>(sql, new { mail });
    }
}
			
				
0 Comments