Dynamics 365 Business Central APIでロケーション別在庫を取得・更新する方法|Items By Location(2554)とFlowFilter/カスタムAPI設計

Dynamics 365 Business Central(Dynamics 365)と在庫補充アプリをAPI連携するとき、標準APIでは在庫が「全ロケーション合算」でしか取れず詰まりがちです。ロケーション別の在庫・売上・発注を“現実的に”扱うための取得(GET)と登録(POST)の手順を、ODataとカスタムAPIまで含めて整理します。

目次

結論:ロケーション別在庫をAPIで扱うルートは「ODataで読む」か「カスタムAPIで作る」

まず最初に結論です。Business Centralの標準REST API(/api/v2.0/…)は、ERPの基本的なエンティティ(Items / Sales Orders / Purchase Orders など)を扱うのに最適化されています。一方で「ロケーション別在庫の集計」のような“分析系”は、標準APIだけで完結しないケースが多いです。

実装の近道は、要件を次の2つに分けて考えることです。

  • 読み取り(GET):ロケーション別在庫は、ODataで公開したクエリ/ページ(もしくはFlowFilter)で取る
  • 更新(POST):在庫数量そのものを直接更新するのではなく、購買・在庫調整など「業務トランザクション」として登録する(標準API or カスタムAPI)
やりたいこと推奨アプローチポイント
ロケーション別在庫(現在庫)を一覧で取得標準クエリ「Items By Location (2554)」をOData公開してGET最短。環境によって2554が無い場合は代替クエリを作成
特定アイテムの「在庫・売上・発注」をロケーション指定で取得Item CardなどのページをOData公開し、FlowFilter(Location_Filter)でGETFlowFields(Inventory / Qty_on_Sales_Order 等)をロケーション指定で計算できる
売上数量(受注残)・発注数量(発注残)をロケーション別に取得標準APIのsalesOrderLines / purchaseOrderLines をlocationIdでフィルタして集計標準APIの行データはロケーション(locationId)を持つ。アプリ側で集計可能
ロケーション別で発注を登録(POST)標準APIでpurchaseOrderLinesを作成し、locationIdをセット「locationCode」ではなくGUIDのlocationIdを使う
ロケーション別在庫を“更新”したい(在庫補充・調整)カスタムAPI(API Page)で「補充依頼」や「ジャーナル入力」を受け、BC側で処理在庫数量は直接書き換えない。仕訳/ジャーナル/受注/発注として残す

なぜ標準APIだと「全ロケーション合算」になりやすいのか

Business Centralの在庫は、内部的には「在庫元帳(Item Ledger Entry)」などのトランザクションから算出されます。UIのアイテムカードや在庫照会では、ロケーションを指定したり、期間を指定したりして“計算結果(FlowField)”を表示します。

しかし標準REST APIのItems(dynamics_item)は、基本的にアイテムマスタとしての情報を返す設計で、在庫関連は全体合算の数値(トータル)になりやすいのが実情です。質問元のMicrosoft Q&Aでも、この手の「ロケーション別在庫をAPIでGET/POSTしたい」という相談に対して具体実装は示されず、サポート窓口案内で終了しています。

したがって、ロケーション別の在庫や受注残/発注残を扱いたい場合は、次のように「データの取り方」を切り替える必要があります。

  • 集計済みを返すクエリ/ページをOData公開する(=BC側で計算させた結果を取る)
  • 行データ(Sales/Purchase lines)をAPIで取得してアプリ側で集計する(=BC側の“明細”を取って積み上げる)
  • 必要なら、カスタムAPIで「集計済みエンドポイント」を自作する

押さえておきたい:Business Centralの「API」は大きく2系統

Business Centralでは、外部連携でよく使うHTTPインターフェースが大きく2種類あります。

種類URLの目安主用途強み注意点
標準REST API(API Pages / API Queries).../api/v2.0/companies({id})/...マスタ・伝票・行のCRUD統合向けに最適化/バージョニング/(API Pageは)Webhook対応標準APIに“欲しい集計”が無いことがある。APIの拡張はできないため、自作が必要
OData Web Services(ページ/クエリ公開).../ODataV4/Company('...')/ServiceName画面相当のデータ参照、クエリ結果の取得標準クエリやFlowFiltersを使った計算結果を取り出せる読み取り中心。UIページ公開は統合用途では過剰な項目が混ざりやすい

「読み取り専用で、複数テーブルを結合して出したい」ならAPI Query、「読み書きが必要」ならAPI Page、という整理が公式ドキュメント上も明確です。

読み取り(GET)の最短手段:標準クエリ「Items By Location (2554)」をODataで公開する

ロケーション別在庫を“一覧”として取りたい場合、最短ルートは標準クエリ「Items By Location (2554)」をWebサービスとして公開し、OData V4でGETする方法です。

Items By Location (2554) が見つからないときの注意点

「2554がどこにも見当たらない」というケースもあります。実は、このクエリはBase Applicationに常に入っているとは限らず、Microsoftの拡張機能(_Exclude_Microsoft Dynamics 365 – Smartlist)側に含まれている例が報告されています。そのため、環境(SaaS / On-Prem、バージョン、地域)によっては存在しないことがあります。

  • 存在する場合:そのままWebサービス公開して使える
  • 存在しない場合:同等のクエリをALで作る(後述の「カスタムAPI(API Query)」)

公開手順(Business Central側の設定)

  1. Business Centralで「Web サービス(Web Services)」ページを開く
  2. 新規行を追加し、以下を設定する
    • Object Type:Query
    • Object ID:2554
    • Service Name:例)ItemsByLocation(任意)
    • Published:Yes
  3. 公開後に表示されるOData URL(OData V4)を控える

ODataのURLは環境ごとに違いますが、概ね次の形式になります。

https://{server or api.businesscentral.dynamics.com}/{tenant}/{environment}/ODataV4/Company('{companyName}')/{serviceName}

取得例(GET):ロケーションで絞る、必要項目だけ取る

ODataでは $filter や $select が使えるため、「必要なロケーションだけ」「必要な列だけ」を返すようにすると速度とデータ量の両方が改善します。

GET .../ODataV4/Company('CRONUS-International-Ltd.')/ItemsByLocation?$filter=Location_Code eq 'GREEN'&$select=Item_No,Description,Location_Code,Inventory

さらに、アイテム番号も指定すれば「特定SKU×特定ロケーション」をピンポイントで取得できます。

GET .../ItemsByLocation?$filter=(Location_Code eq 'GREEN') and (Item_No eq '1906-S')

ODataのフィルタ式や書き方の詳細は公式のフィルタ式ドキュメントが参考になります。

この方法でどこまで取れる?(在庫・売上・発注)

「Items By Location (2554)」が返す列は環境/バージョンで差があり得ますが、少なくとも「アイテム×ロケーション」の観点で在庫関連を見られるのがポイントです。もし売上数量(受注残)・発注数量(発注残)まで同じ1行で返したいなら、次の章のFlowFiltersまたはカスタムAPIがより確実です。

ロケーション別の「在庫・売上・発注」をまとめて取りたいなら:FlowFilters(Location_Filter)を使う

「一覧」ではなく、特定アイテムの在庫/受注残/発注残をロケーション別で取りたい場合は、ページをOData公開してFlowFiltersを使うのが強力です。

公式ドキュメントでは、Item CardページをWebサービス公開し、Location_Filter を指定してFlowFields(例:Qty_on_Sales_Order など)がロケーション別に計算される例が紹介されています。

手順イメージ

  1. Item Card(例:ページ30)をWebサービスとして公開(Service Name例:ItemCard)
  2. ODataのメタデータで利用可能なFlowFilters(Location_Filter 等)を確認
  3. 取得時に $filter=Location_Filter eq 'GREEN' のように指定して呼び出す

取得例(GET):同じアイテムでもFlowFieldがロケーション指定で変わる

GET .../ODataV4/Company('CRONUS-International-Ltd.')/ItemCard('1906-S')?$filter=Location_Filter eq 'GREEN'

この方法のメリットは、在庫(Inventory)だけでなく、受注残・発注残などの「業務的に欲しい数量」も、BC標準ロジックで算出した値を取れる点です。ロケーション別在庫を“定義どおり”に取りたい現場では、非常に事故が少ない選択肢になります。

売上数量(Sales Quantity)・発注(Purchase Orders)をロケーション別に扱う:標準REST APIで「行」を取って集計する

「売上数量」「発注数量」は、在庫のように“残”や“未処理”の定義が絡みます。アプリ側で運用定義が決まっているなら、標準APIで行データを取得し、ロケーション別に集計するのが実務的です。

前提:標準APIは locationCode ではなく locationId(GUID)で持つ

Business Centralの標準APIでは、受注行・発注行にロケーションが入りますが、指定するのは多くの場合 locationId(GUID) です。つまり、連携アプリ側に「ロケーションコード(例:GREEN)」がある場合は、まず locations APIでコード→IDに変換します。

location リソースでは、code と id が定義されています。

ロケーションコード→locationId を取得する例

GET https://{businesscentralPrefix}/api/v2.0/companies({companyId})/locations?$filter=code eq 'GREEN'

レスポンスから id を取り出して保持しておきます(キャッシュ推奨)。以降、受注行・発注行のフィルタやPOSTにこのGUIDを使います。

受注行(Sales Order Lines)をロケーション別で作成・取得する

salesOrderLines の作成例では、リクエストボディに locationId を指定できます。これにより、受注行をロケーションに紐づけて登録できます。

POST https://{businesscentralPrefix}/api/v2.0/companies({companyId})/salesOrders({salesOrderId})/salesOrderLines
Content-Type: application/json

{
"itemId": "0ea6738a-44e3-ea11-bb43-000d3a2feca1",
"lineType": "Item",
"lineObjectNumber": "1996-S",
"quantity": 12,
"unitOfMeasureCode": "PCS",
"locationId": "00000000-0000-0000-0000-000000000000"
}

取得(GET)については、salesOrderLinesを locationId で絞り込み、アプリ側で数量を積み上げると「ロケーション別の売上数量(例:受注残)」を作れます。

  • 受注残が欲しい:出荷済み/請求済みとの差分の取り方を定義し、APIが返すフィールドで計算
  • 期間別が欲しい:shipmentDate など日付系でフィルタして集計

発注行(Purchase Order Lines)をロケーション別で作成・取得する

purchaseOrderLines も同様に、作成時に locationId を指定できます。公式の作成例でも locationId が含まれています。

POST https://{businesscentralPrefix}/api/v2.0/companies({companyId})/purchaseOrderLines
Content-Type: application/json

{
  "documentId": "960f5c9c-44e3-ea11-bb43-000d3a2feca1",
  "sequence": 10000,
  "lineType": "Item",
  "lineObjectNumber": "1996-S",
  "quantity": 12,
  "unitOfMeasureCode": "PCS",
  "expectedReceiptDate": "2025-01-15",
  "locationId": "00000000-0000-0000-0000-000000000000"
}

これにより「ロケーション別の発注残(未入荷)」は、発注行をロケーションで絞り、未受領数量を集計することで作れます。

ロケーション別の数量は“どの数量を指すか”で結果が変わる:定義を先に固定する

在庫補充アプリの連携では、「在庫」「売上数量」「発注数量」という言葉が同じでも、現場では次のように意味がぶれることがあります。API実装前に、どの数量を使うかを決めておくと、後工程の手戻りが激減します。

言い方現場でよくある意味典型的な取り方注意点
在庫数量(Inventory levels)現在庫(オンハンド)ODataのクエリ/FlowField、または元帳から算出倉庫管理(Bin/ロット/シリアル)を使うと「利用可能」の定義が増える
売上数量(Sales Quantity)受注残(まだ出荷していない数量)salesOrderLinesをlocationIdで取得し、未出荷分を計算出荷済み・請求済みの扱いを統一
発注数量(Purchase Orders)発注残(まだ入荷していない数量)purchaseOrderLinesをlocationIdで取得し、未受領分を計算部分入荷、検収、入庫タイミングの運用差に注意
引当済み(予約)他の受注に確保されていて使えない数量FlowFieldや専用照会(要件次第でカスタム)「在庫 − 引当」を補充基準にするなら必須

「GETもPOSTもしたい」場合の現実解:カスタムAPI(API Page / API Query)を作る

ここまでの方法で「取得(GET)」はかなりの範囲をカバーできます。一方、質問のようにロケーション別で“補充”や“調整”をPOSTしたい場合、標準APIの範囲だけで完結しないことがあります。

このときの実務的な落としどころは次の2パターンです。

  • 標準APIでできる更新は標準APIで行う(発注行にlocationIdを入れて作成する等)
  • 標準APIで足りない更新は、カスタムAPIに「依頼データ」をPOSTし、BC側で業務処理として反映する

公式ドキュメントでも、読み書きが必要ならAPI Page、読み取り専用ならAPI Queryという使い分けが明示されています。また、API Page/Queryは拡張できないため、要件に合わせて新規作成が前提になります。

カスタムAPI設計の“おすすめ”:在庫を直接更新しない

在庫は結果であって入力ではありません。外部アプリが「在庫数量を上書き」してしまうと、監査・原価・ロット追跡・棚卸などで破綻しやすくなります。

そこでおすすめは、外部アプリは次のどちらかをPOSTする形にすることです。

  • 補充依頼(Replenishment Request):アイテム・ロケーション・希望数量・必要日などをPOSTし、BC側で発注や移動を作成
  • 在庫調整ジャーナルの“下書き”:調整理由・アイテム・ロケーション・数量をPOSTし、BC側で承認後に転記

カスタムAPI(API Page)の最小例(概念コード)

ここでは「補充依頼」を受け取る最小例のイメージを示します。実案件では、権限、エラーハンドリング、冪等性(同一外部IDの二重登録防止)を必ず入れてください。

table 50100 "Replenishment Request"
{
    DataClassification = CustomerContent;

    fields
    {
        field(1; "SystemId"; Guid) { }
        field(10; "ExternalDocumentNo"; Code[50]) { }
        field(20; "ItemNo"; Code[20]) { }
        field(30; "LocationCode"; Code[10]) { }
        field(40; "Quantity"; Decimal) { }
        field(50; "RequestedDateTime"; DateTime) { }
        field(60; "Status"; Option) { OptionMembers = New,Processed,Error; }
        field(70; "ErrorMessage"; Text[250]) { }
    }

    keys
    {
        key(PK; "SystemId") { Clustered = true; }
        key(External; "ExternalDocumentNo") { }
    }
}

page 50100 "Replenishment Requests API"
{
    PageType = API;
    APIPublisher = 'contoso';
    APIGroup = 'replenishment';
    APIVersion = 'v1.0';
    EntityName = 'replenishmentRequest';
    EntitySetName = 'replenishmentRequests';
    SourceTable = "Replenishment Request";
    DelayedInsert = true;
    ODataKeyFields = SystemId;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field(externalDocumentNo; Rec.ExternalDocumentNo) { }
                field(itemNo; Rec.ItemNo) { }
                field(locationCode; Rec.LocationCode) { }
                field(quantity; Rec.Quantity) { }
                field(requestedDateTime; Rec.RequestedDateTime) { }
                field(status; Rec.Status) { Editable = false; }
                field(errorMessage; Rec.ErrorMessage) { Editable = false; }
            }
        }
    }
}

このAPIに外部アプリからPOSTすると、BC側に「補充依頼」が溜まります。あとはBC側でジョブキュー(Job Queue)や手動処理で、依頼を元に発注書作成/移動オーダー作成/在庫調整など、業務に沿った処理を行います。

外部アプリ側のPOST例(補充依頼)

POST https://{businesscentralPrefix}/api/v2.0/{publisher}/{group}/{version}/companies({companyId})/replenishmentRequests
Content-Type: application/json

{
"externalDocumentNo": "APP-REQ-20251221-0001",
"itemNo": "1906-S",
"locationCode": "GREEN",
"quantity": 20,
"requestedDateTime": "2025-12-21T10:30:00Z"
}

この方式にすると、標準APIの制約(在庫調整の直接入力の難しさ、業務ルールの多さ)を回避しながら、外部アプリの要求(ロケーション別に補充したい)を自然に満たせます。

実装でハマりやすいポイント(ロケーション別在庫連携の落とし穴)

最後に、ロケーション別在庫を連携する際に“よく事故る”論点をまとめます。最初にチェックしておくと、API設計・運用がかなり安定します。

論点症状回避策
locationCode と locationId の混同POSTは通るがロケーションが入らない/フィルタできない標準APIはlocationId(GUID)を使う。locationsでコード→IDをマッピングしてキャッシュ
「在庫」の定義が曖昧UIの数字とAPIの数字が合わないオンハンド/受注残/引当/発注残など、補充判断に使う数量を定義して固定
FlowField/FlowFilterの理解不足ロケーション指定のつもりが全体合算になるOData公開ページでFlowFilter(Location_Filter等)を使い、BC標準計算結果を取る
倉庫管理(Bin/ロット/シリアル)ロケーションは合っているが、実際に引当可能な在庫が違う要件が「利用可能在庫」なら、ビンやトラッキングも含めた設計に(必要ならカスタムAPI)
在庫を直接更新したい整合性が崩れる/監査・原価・棚卸で破綻発注・移動・在庫調整ジャーナルなど、業務トランザクションとしてPOSTする

まとめ:最短は「Items By Location / FlowFilters」でGET、更新は「標準API+足りない分はカスタムAPI」

  • 標準REST API(items等)だけでロケーション別在庫を取ろうとすると、合算値しか出ず詰まりやすい
  • 一覧でロケーション別在庫を取りたいなら、まずは標準クエリ「Items By Location (2554)」をOData公開してGET
  • 特定アイテムの在庫・受注残・発注残をロケーション指定で確実に取りたいなら、OData公開ページ+FlowFilter(Location_Filter)が強い
  • 発注や受注のロケーション指定は、標準APIでも locationId を使えばPOSTできる
  • 在庫補充の“更新”は、在庫数量を直接更新せず、補充依頼やジャーナル下書きを受けるカスタムAPIで業務処理に落とすのが堅牢

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次