用友 U9 V60 カスタム開発技術の要点整理

UBF フレームワーク設計

カスタム機能を実装する際には、オブジェクト指向の原則に基づいて構造を定義する必要があります。主に以下の 3 つのアプローチが用いられます。

  • 継承(Inheritance): ベースクラスの機能を拡張するために使用します。
  • 組み合わせ(Composition): ビジネスロジックをコンポーネントに分解して連携させます。
  • 状態機械(State Machine): ドキュメントライフサイクルの遷移を制御するために不可欠です。

ビジネスエンティティ(BE)の実装

BE レベルでの実装では、標準属性のカスタマイズと初期値処理に重点を置きます。

ドキュメントタイプ属性のオーバーライド

特定の論理でドキュメントタイプを取得する際、プロパティを明示的に再定義します。

public override U9.Core.DocType DocType
{
    get
    {
        // ドキュメント種別情報へのアクセスポイント
        return this.GetDocumentType();
    }
}

初期値設定フック

新規作成時のデフォルト値設定には、イベントフックを利用します。システム組織情報を取得する例は以下の通りです。

/// <summary>
/// デフォルト値セット時の処理
/// </summary>
protected override void OnInitializeDefaultValues()
{
    // 組織情報が未設定の場合はログインユーザーの所属組織を設定
    if (string.IsNullOrEmpty(this.OrgID))
    {
        this.OrgID = SecurityContext.GetCurrentOrgId();
    }

    base.OnInitializeDefaultValues();
}

ビジネスプロセス(BP)開発

BSP 実装時、AOP フレームワークやデータベース操作クラスを適切に呼び出す必要があります。

AOP エンジン利用

コード内クエリを実行する場合、Runtime ライブラリを参照し、スコープ制御を行います。

// Runtime\\UFSoft.UBF.AopFrame.dll を参照
using (var processor = new BPProcessor())
{
    // ロジック実装
}

データベースアクセス例外ハンドリング

SQL 実行やデータ検索を行う際は、適切な DLL 引用(`Util.DataAccess.dll` など)を行い、例外が発生しないよう防御策を講じます。

// データベース接続情報の取得と SQL 実行
DataAccessManager.ExecNonQuery(
    DBManager.GetCurrentConn(), 
    @"INSERT INTO LogTable (Msg) VALUES (@msg)", 
    null
);

ファインダーによる検索

パラメータ付き検索を行う場合、OqlParam オブジェクトを使用して安全に値を渡します。

var period = BudgetPeriod.Finder.FindMany("Year = @y AND Scheme = @s ORDER BY Num DESC",
    new OqlParam[]
    {
        new OqlParam("y", period.Year.ID),
        new OqlParam("s", scheme.ID),
    });

if (period.Count > 0)
{
    var nextNum = period[0].Num + 1;
}

データセッション管理

トランザクション単位で変更を保存するには、Session の Open と Commit を使用します。

using (var trx = Session.BeginTransaction())
{
    try 
    {
        // クライアント側のデータ更新処理
        trx.Commit();
    }
    catch
    {
        throw;
    }
}

コンテキスト情報参照

帳簿や組織に関する情報を現在のセッションから参照するには、専用のコンテキストプロパティを利用します。

// 帳簿情報
var primarySOB = PDHelper.Context.PrimarySOBCode;

// 組織 ID 参照
var currentOrgID = PDHelper.Context.OrgId;

グリッド行の追加

行項目を持つグリッドモデルに対して、新しい行レコードを追加します。

model.Lines.AddNewRow();

ユーザーインターフェース(UI)制御

フォームロードおよびバインドタイミングでの制御処理が行われます。

子コントロール作成後

メソッド:AfterCreateChildControls()

ここでは参照ダイアログの登録、行番号の自動設定、削除確認メッセージの設定などを行います。

protected override void AfterCreateChildControls()
{
    base.AfterCreateChildControls();
    
    // 参照ウィンドウの登録
    PDUI.ShowDialogForm(Guid.Parse("4540e880-7eb7-4eba-8da7-f1889b092af8"), ...);

    // グリッドの初期化
    SetLineNoSequence(DataGridMain);
    
    // 削除ボタンの確認設定
    MessageManager.PromptDeleteConfirm(Page, BtnDelete, "削除を実行してもよろしいですか?");
}

// 行番号シーケンス設定ヘルパー
private void SetLineNoSequence(IUFDataGrid grid)
{
    var colIndex = grid.Columns["DocLineNo"].Index;
    ((ISequenceColumn)grid.Columns[colIndex]).AutoSeq = true;
    ((ISequenceColumn)grid.Columns[colIndex]).Step = 1;
}

UI モデルバインド後

メソッド:AfterUIModelBinding()

権限設定、拡張フィールド、非同期処理などの制御を行います。

protected override void AfterUIModelBinding()
{
    base.AfterUIModelBinding();

    // ボタン権限の適用
    PermissionService.ApplyButtonAuth(Page, "BtnSubmit");

    // 拡張フィールドの表示制御
    FlexFieldHelper.Bind(GridItem, 1);

    // クラスターフィルタ条件の追加
    GridCols["ProductType"].CustomFilter = "ZoneDef.Code = 'Z30'";
}

ステート管理とナビゲーション

ASP.NET Session を利用した状態保持や、ページ間の移動処理も重要になります。

protected void OnLoadDefaultHandler(object sender, EventArgs e)
{
    // セッションからの ID 取得
    var targetIdStr = Session["TargetPageID"]?.ToString();
    Session.Remove("TargetPageID");

    if (long.TryParse(targetIdStr, out long idVal))
    {
        ActionManager.NavigateTo(idVal);
    }
}

URL パラメータ操作

Web Part やネイティブパラメータを通じて、外部からの起動情報を渡すことができます。

var param = new NavigateParameter();
param.Params.Add("ProjectCode", ProjectCode);
NavigateManager.LoadForm(Page, FormGUID, TaskId, Width, Height, param);

リストクリックや詳細カード表示の遷送ロジックは以下のようになります。

private void RowDoubleClick_Handler(object sender, UIActionEventArgs args)
{
    if (View.FocusedRec != null)
    {
        var recordId = View.FocusedRec.GetLongID("ID");
        MoveToCardScreen(recordId);
    }
    DefaultHandler.Invoke(this, args);
}

private void MoveToCardScreen(long dataId)
{
    var nameVals = new NameValueCollection();
    nameVals["Status"] = "Browse";
    nameVals["ID"] = dataId.ToString();
    nameVals["Source"] = "List";

    CurrentPart.Navigate(FormGuid_Card, nameVals);
}

ワークフロー承認制御

承認、提出、棄審の状態遷移を状態機械とエンティティステータスの双方を考慮して制御します。

提出処理

private void SubmitRequest(List<DocDTO> documents)
{
    foreach (var doc in documents)
    {
        var entity = DocFinder.FindById(doc.Id);
        
        if (VerifyVersion(entity, doc.Version))
        {
            using (var session = Session.Begin())
            {
                if (entity.Status == Status.Opened)
                {
                    entity.Status = Status.WorkflowPending;
                }
                
                // 状態機械イベント発火
                if (entity.DocType.ConfirmType == ConfirmTypeEnum.ApproveFlow)
                {
                    entity.StateInstance.Submit(new SubmitEvent());
                }
                session.Commit();
            }
        }
        else
        {
            throw new ConflictException("バージョン不一致");
        }
    }
}

承認処理

private void ApproveRequest(List<DocDTO> documents)
{
    // ... 同様の検証ロジック ...
    using (var session = Session.Begin())
    {
        if (entity.Status == Status.WorkflowPending)
        {
            entity.Status = Status.Approved;
            entity.ApprovalDate = DateTime.Now;
            
            // ステートマシン通過
            if (entity.DocType.ConfirmType == ConfirmTypeEnum.ApproveFlow)
            {
                entity.StateInstance.Approve(new ApprovalEvent());
            }
            session.Commit();
        }
    }
}

棄審処理

private void RejectRequest(List<DocDTO> documents)
{
    // ... 同様の検証ロジック ...
    using (var session = Session.Begin())
    {
        if (entity.Status == Status.Approved)
        {
            entity.Status = Status.Opened;
            
            if (entity.DocType.ConfirmType == ConfirmTypeEnum.ApproveFlow)
            {
                entity.StateInstance.Reject(new ResultEvent());
            }
            session.Commit();
        }
    }
}

プラグイン展開とパッケージング

最終的なビルド成果物として、アセンブリバージョンの管理やmanifest定義を用いたパッケージ作成が必要です。

// アセンブリバージョン情報設定例
[assembly: AssemblyVersion("1.0.0.1")]
[assembly: AssemblyFileVersion("1.0.0.1")]
[assembly: AssemblyInformationalVersion("v1.0 Release Candidate 1")]

// パッケージ生成スクリプトイメージ
// 通常は Ant または MSBuild 等を使用
// dotnet pack --configuration Release --output ./dist

タグ: U9 UBF C# Workflow StateMachine

8月5日 00:06 投稿