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

整體的概念上如上圖
一等一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