# Order Line Master — 受注

受注明細を Last Mile の受注 SoT (`order_line_master`) に取り込みます。**1 行 = 1 販売単位**（数量の概念を持ちません。5 個販売は 5 行 = `line_instance_no` 1〜5）。商品は `barcode`、販売チャネル・拠点は ID で結合します。

> **Base URL** `https://api.lmile.io` · **認証** `x-ingest-secret` ヘッダー · `shop` はリクエストボディに含めます

## エンドポイント

| Method | Path                                 | 用途                       |
| ------ | ------------------------------------ | ------------------------ |
| `POST` | `/api/order-line-master/bulk-upsert` | 受注明細の一括 upsert（per-unit） |

## 認証

```http
x-ingest-secret: {INGEST_SECRET}
Content-Type: application/json
```

| Header            | Required | 説明                           |
| ----------------- | :------: | ---------------------------- |
| `x-ingest-secret` |     ✓    | ingest API シークレット（サーバーサイドのみ） |

***

## `POST` /api/order-line-master/bulk-upsert

受注明細を per-unit で一括登録・更新します。一意キー `(shop, source_system, transaction_id, transaction_line_id, line_instance_no)` で UPSERT し、変更を `order_line_master_update_logs` に記録します。

### リクエスト

```http
POST https://api.lmile.io/api/order-line-master/bulk-upsert
x-ingest-secret: {INGEST_SECRET}
Content-Type: application/json
```

```json
{
  "shop": "demo-store.myshopify.com",
  "dry_run": false,
  "change_source": "shopify_sync",
  "ingest_run_id": "run-2026-06-02-001",
  "idempotency_key": "shopify-2026-06-02-001",
  "rows": [
    {
      "source_system": "shopify",
      "transaction_id": "4567890123",
      "transaction_line_id": "1",
      "line_instance_no": 1,
      "barcode": "1234567890123",
      "ordered_at": "2026-06-02T12:34:56+09:00",
      "currency": "JPY",
      "is_test": false,
      "channel_id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
      "location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "customer_id": "3097658523781",
      "gross_amount_taxincl": 9800,
      "discount_amount_taxincl": 800,
      "net_amount_taxincl": 9000,
      "net_amount_taxexcl": 8182,
      "tax_amount": 818,
      "tax_rate": 0.10,
      "refund_amount": 0,
      "refunded_at": null,
      "refund_reason": null
    }
  ]
}
```

### ボディパラメータ

| パラメータ                | 型             |  必須 | 説明                               |
| -------------------- | ------------- | :-: | -------------------------------- |
| `shop`               | string        |  ✓  | 店舗ドメイン                           |
| `rows`               | array         |  ✓  | 受注明細行の配列（最大 5,000 行/バッチ）         |
| `dry_run`            | boolean       |     | `true` で検証のみ（書込なし）               |
| `change_source`      | string        |     | 変更ログの出所（default: `external_app`） |
| `ingest_run_id`      | string        |     | 取込ラン ID（ログに記録）                   |
| `idempotency_key`    | string        |     | 監査用キー（ログに記録）                     |
| `changed_by_user_id` | string (uuid) |     | 変更実行ユーザー                         |

#### `rows[]` オブジェクト（1 行 = 1 販売単位）

| パラメータ                     | 型             |  必須 | 説明                           |
| ------------------------- | ------------- | :-: | ---------------------------- |
| `source_system`           | string        |  ✓  | 取込元（`shopify` / `smaregi` 等） |
| `transaction_id`          | string        |  ✓  | 取引（注文）ID                     |
| `transaction_line_id`     | string        |  ✓  | 取引明細 ID                      |
| `line_instance_no`        | integer (≥1)  |  ✓  | 明細内の販売単位連番（数量展開。3 個なら 1,2,3） |
| `barcode`                 | string        |  ✓  | 商品 barcode                   |
| `ordered_at`              | ISO 8601      |  ✓  | 注文日時                         |
| `currency`                | string        |  ✓  | 通貨コード                        |
| `is_test`                 | boolean       |  ✓  | テスト注文フラグ                     |
| `channel_id`              | string (uuid) |  ✓  | 販売チャネル ID（`sot.channels`）    |
| `location_id`             | string (uuid) |  ✓  | 拠点 ID（`sot.locations`）       |
| `customer_id`             | string        |     | 顧客識別子（外部 ID、null 可）          |
| `gross_amount_taxincl`    | number        |  ✓  | 税込総額（割引前）                    |
| `discount_amount_taxincl` | number        |  ✓  | 税込割引額                        |
| `net_amount_taxincl`      | number        |  ✓  | 税込正味額                        |
| `net_amount_taxexcl`      | number        |  ✓  | 税抜正味額                        |
| `tax_amount`              | number        |  ✓  | 税額                           |
| `tax_rate`                | number        |  ✓  | 税率（0〜1。10% は `0.10`）         |
| `refund_amount`           | number        |  ✓  | 返金額（≥0）                      |
| `refunded_at`             | ISO 8601      |     | 返金日時（null 可）                 |
| `refund_reason`           | string        |     | 返金理由（null 可）                 |

#### バリデーション

* **金額整合**（許容誤差 0.01）: `gross_amount_taxincl − discount_amount_taxincl ≈ net_amount_taxincl`、`net_amount_taxincl − tax_amount ≈ net_amount_taxexcl`
* `tax_rate` は `[0, 1]`、`refund_amount` は `≥ 0`
* `channel_id` / `location_id` が当該 shop に存在しない行は **reject**（`errors[]`）
* `barcode` が商品マスター未登録でも **投入**（`warnings[]`。`agent_read` 上で商品名/SKU が NULL になるだけ）

### レスポンス

```json
{
  "ok": true,
  "dry_run": false,
  "accepted_rows": 48,
  "inserted_rows": 20,
  "updated_rows": 28,
  "rejected_rows": 2,
  "warnings_count": 1,
  "errors": [
    { "row_index": 5, "field": "location_id", "code": "location_not_found", "message": "location_id not found ..." }
  ],
  "warnings": [
    { "row_index": 7, "field": "barcode", "code": "product_not_in_master_warning", "message": "barcode not found in product master ..." }
  ],
  "adapter_version": "lm463_order_line_master_v1"
}
```

| フィールド                            | 説明                                         |
| -------------------------------- | ------------------------------------------ |
| `accepted_rows`                  | upsert 対象として受理した行数                         |
| `inserted_rows` / `updated_rows` | 新規 / 更新の件数                                 |
| `rejected_rows`                  | バリデーションで除外した行数（`errors[]`）                 |
| `warnings_count` / `warnings[]`  | 投入したが要注意の行（商品マスター未登録 等）                    |
| `errors[]`                       | `row_index` / `field` / `code` / `message` |

### 挙動・冪等性

* **一意キー**: `(shop, source_system, transaction_id, transaction_line_id, line_instance_no)` で UPSERT。同一キーの再送は安全（差分があれば更新、無ければ実質 no-op）。
* **変更ログ**: `order_line_master_update_logs` に `change_type`（`insert` / `update` / `refund`）と `before_data` / `after_data` / `changed_fields` を記録。
* **per-unit**: 数量列は持ちません。数量 N の明細は `line_instance_no` 1〜N の N 行で表現します。
* 税・返金は受信時に `order_line_master` の列として反映されます（別途の refund allocation pipeline は持ちません）。

***

## エラー

| Status | 条件                    | レスポンス例                                                      |
| :----: | --------------------- | ----------------------------------------------------------- |
|  `401` | `x-ingest-secret` 不一致 | `unauthorized`                                              |
|  `400` | ボディ形式エラー / バッチ上限超過    | `{ "ok": false, "error": "rows[] required and non-empty" }` |
|  `500` | サーバー内部エラー             | `{ "ok": false, "error": "..." }`                           |
|  `503` | `INGEST_SECRET` 未設定   | `{ "ok": false, "error": "INGEST_SECRET not configured" }`  |

行単位の不備は `200` のレスポンス内 `errors[]`（reject）/ `warnings[]` として返り、正常行の投入は継続します（silent skip しません）。

## データモデル

* `order_line_master` — 受注明細 SoT（1 行 = 1 販売単位）
* `order_line_master_update_logs` — 変更監査ログ（`insert` / `update` / `refund`）
* read は `agent_read.order_line_master`（safe view）経由


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lmile.gitbook.io/lmile-docs/ingestion-apis/order-line-master.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
