BPM 電子表單透過 WebService+程式透過ERP外部起單(1) – 起單 | ERP |ERP |UOF2 |asmx| 一等一科技

by | 10 月 14, 2025 | 一等一UOF系統, 程式 | 0 comments

Views: 4

這篇要教怎麼做外部起單

整體的概念上如上圖

一等一BPM的幾種起單方式

  1. 資料庫起單
  2. 程式起單(本範例用這個)
  3. 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:\某資料夾\"/>

將表單的資料InsertTB_WKF_EXTERNAL_TASK這個資料表

WebService起單

呼叫站台的網址 /PublicAPI/WKF/WKF.asmx

方法為 SendForm(string token,string formInfo)

tokenWebService取得的tokenformInfo放表單資料

這個做法缺點是要透過另一個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中(這個案例不處理這段),開發的

  1. 先在EIP上設計表單
  2. 發佈表單
  3. 從資料庫取得表單範例XML
  4. 寫一個Web Servicer接收資料後,組出XML呼叫Ede.Uof.WKF.Utility.TaskUtility.WebService_CreateTask(string formInfo)

設計一個簡單表單

Image

這邊就請依照實際需求調整,

表單名稱:測試外部起單

  • 表單編號DOC_NBR
  • ERP單號 ERP_NBR 單行文字
  • 申請日期CREATE_DATE 日期欄位
  • 主旨SUBJECT 單行文字

簽核流程設定

一開始設定時只要設定一站申請者自己簽就好,上線

Image

表單標題

這邊請依照需求自行設定

Image
Image

發佈表單

Image

設計的差不多就勇敢地把表單發佈出去

Image

表單的設定

不要勾[下一站若為相同簽核者則跳過]

Image

寫Web Service

這邊在Visual Studio中開啟站台的專案,然後在CDS下新增一個WS的資料夾,作為等下寫WebService用

Image

然後我們要先在WS下新增一個Web.config檔案

Image

對剛新增的WS資料夾按右鍵>Add 新增 > Add New Item 新增項目

Image

選Web Configuration file (我記得翻譯成 Web組態設定檔),選到之後副檔名會是Web.config,然後Add 新增

接著新增內容如下WKF.asmx

Image

<?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的檔名

Image

接著我們要新增WebService的程式,這邊同樣Add > Add New Item

Image

  1. 選WebService(ASMX) -> Visual C#
  2. 命名為WKF.asmx 這邊就看你想取名為什麼
  3. 不要勾 Place code in seoarate file (這點重要)
  4. Add

Image

接著測試站台

Image

先測試這個頁面有沒有東西 在網址輸入 站台網址/CDS/WS/WKF.asmx

如果沒出現通常是web.config有問題,或者網址路徑有問題

取得表單的XML

先申請一張單,然後按下儲存

Image

接著到資料庫透過SQL查詢草稿內容

SELECT TOP (1) [CONTENT]   FROM [dbo].[TB_WKF_SCRIPT]  ORDER BY MODIFY_TIME DESC

Image

Image

CONTENT點進去後如上圖,這是剛剛儲存的表單資料,這邊的資料我們先複製到程式當中

Image

我在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來的表單欄位

Image

這邊的重點是有個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;
}

這邊我做了幾件事

  1. 新增docXml這個字串變數var docXml = $@"表單的xml內容..";
  2. 把原本註解的xml貼進去docXml當中
  3. fillerNamefillerUserGuidfillerAccount 的內容都清掉
  4. 把起單的帳號跟欄位的fieldValue換成對應的變數,這邊的寫法是{XmlEscape(變數)}
  5. 加上起單帳號

測試起單資料

同樣透過瀏覽器瀏覽WebService的網址,然後寫剛剛寫好的Start

Image

填寫資料 > 叫用

Image

測試成功

Image

檢視回傳結果

成功訊息

<?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

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *