> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alforse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# アーキテクチャ

> Alforse スタックの開発者向けガイド。2 つのアプリケーション、バックエンドゲートウェイ、権限と行レベルセキュリティ、データベース、アウトボックスとランナー、ワークフローと CAD のエンジンを説明します。

Alforse と連携する開発者や、画面の裏で何が行われているかを知りたい方向けのページです。まず 1 回の書き込みがブラウザーからデータベースに到達して戻るまでをたどり、その後、各レイヤーを 1 つずつ見ていきます。

## 概要

Alforse は、1 つの製造データモデルを共有する 2 つのアプリケーションで構成されます。

| アプリケーション | ホスト | 目的 |
| - | - | - |
| **ERP** | `app.alforse.com` | 業務側を担います。見積、購買、計画、在庫、会計、出荷。 |
| **MES** | `mes.app.alforse.com` | 現場の作業を記録・指示します。通常は各作業区のタブレットから使います。 |

どちらの背後にも、1 つのバックエンドがあります。

### このページの読み方

まず[システムの全体像](#the-system-at-a-glance)を読み、次に[1 回の書き込みを端から端まで追う](#tracing-one-write-end-to-end)を読んでください。後者は前者を前提としています。それ以降の節は独立しており、どの順序でも読めます。

専門用語は、ページ末尾の[用語集](#glossary)にある短い定義にリンクしています。[Postgres](#postgres) はその一例です。

NOTE

**パスや件数は変わることがあります**

以下のファイルパス、行番号、件数はすべて、執筆時点のリポジトリの状態を反映したものです。保証ではなく、目安として扱ってください。

***

## システムの全体像

すべてが 1 つの[モノレポ](#monorepo)にあり、pnpm ワークスペースと [Turborepo](#turborepo) で運用され、2 つの層で構成されています。

### 画面

2 つの React Router アプリケーション `apps/erp` と `apps/mes` が、サーバー上で React を描画します。共通のコードは `packages/web/*` にパッケージ化されており、`auth`、`database`、`api`、`jobs`、`planning`、`react`、`utils`、`workflows` などがあります。

### バックエンド

アプリケーションホストは `apps/backend` ただ 1 つです。API の提供に加えて、次のネイティブレイヤーを構成します。

| フォルダー | 内容 |
| - | - |
| `kernel/` | 認可、ID、承認、イベント、レコード、ワークフローなど、すべての領域が共有する契約と、トランザクションポート。 |
| `modules/` | 製品をまたいで使うマスターデータ。単位、製品、住所付きの取引先、文書採番、人員などです。 |
| `products/` | 17 の製品領域。`inventory-and-logistics`、`quality-management`、`source-to-pay` はそのうちの 3 つです。 |
| `packages/database/` | SQL マイグレーション、それを適用するランナー、Postgres ストア。 |

すべてのデータは 1 つの [Postgres](#postgres) データベース（システムオブレコード）に保存されます。これは会社ごとに行レベルの権限を適用し、バックグラウンド処理に入力を供給するトランザクショナルアウトボックスも含んでいます。各要素は[レイヤーごとの解説](#layer-by-layer)で説明します。

### すべてを形作る 5 つの性質

1. **画面はデータベースに直接アクセスしません。** ERP と MES のサーバーコードは、`/api/web` にマウントされたバックエンドゲートウェイと通信します。ゲートウェイは、画面が当初書かれた時と同じ REST、RPC、認証、ストレージのプロトコルを理解し、各呼び出しを `kernel/`、`modules/`、`products/`、`packages/database/` のいずれかにあるネイティブオーナーに渡します。
2. **データベースがテナントを分離します。** 行レベルセキュリティにより、ある会社の行に別の会社からはアクセスできません。しくみは[認証と認可](#authentication-and-authorization)で説明します。
3. **関連する書き込みは 1 つとしてコミットされます。** ネイティブオーナーは呼び出し元の作業単位に参加し、発生したイベントはそのトランザクション内でアウトボックスに入ります。
4. **別個のジョブシステムはありません。** バックエンドプロセス内のランナーが、スケジュールされたタスクとキューイングされたタスクを処理し、配信ワーカーが通知と Webhook を送信します。詳細は[書き込みの後](#after-the-write-events-and-background-work)にあります。
5. **CAD 処理もバックエンドにあります。** ジオメトリ変換と組立計画は、どちらも `/api/web/assembler` の背後で行われ、そのために追加でデプロイするものはありません。

***

## 1 回の書き込みを端から端まで追う

例として、購買担当者が新しい発注書を保存する場合を取り上げます。リクエストがフォーム、検証、権限チェック、データベースへの書き込み、文書番号の割り当てを経て、最後に更新された画面になるまでをたどります。

### 全行程を 1 つの図で

```mermaid theme={null}
sequenceDiagram
  participant B as Browser
  participant E as ERP server (React Router)
  participant G as Backend gateway
  participant P as Postgres
  B->>E: POST form
  E->>E: validate with zod schema
  E->>G: requirePermissions(create: purchasing)
  G-->>E: permission decision
  E->>G: rpc get_next_sequence
  G-->>E: allocate PO000123
  E->>G: insert purchaseOrder + children
  G->>P: write rows and outbox row
  E-->>B: 302 redirect
  B->>E: GET the new order page
  E->>G: loader reads through the gateway
```

行が保存された時点で、リクエストは完了します。通知や Webhook などの副作用は後で、アウトボックスから拾われて処理されます。

### コードで順を追って

#### 1. ボタン

`apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrdersTable.tsx:535`

```tsx theme={null}
<New label={t`Purchase Order`} to={path.to.newPurchaseOrder} />
```

これは送信ボタンではなく、フォームへのリンクである点に注意してください。UI では、ユーザーに許可されていない操作を `permissions.can(...)` が非表示にしますが、これはあくまで配慮です。実際の判断はサーバーで行われます。リンクは `apps/erp/app/utils/path.ts` のルートレジストリである `path.to.*` を経由するため、アプリケーションの URL を手で書くことはありません。

#### 2. フォーム

`apps/erp/app/modules/purchasing/ui/PurchaseOrder/PurchaseOrderForm.tsx:116`

フォームコンポーネントは `ValidatedForm`（パッケージ `@alforse/web-form`）です。内部は通常の HTML フォームで、その上にスキーマのエラーを、ブラウザー由来でもサーバー由来でも表示します。バリデーターと既定値は props で受け取ります。

#### 3. スキーマ

`apps/erp/app/modules/purchasing/purchasing.models.ts:146`

ルールは [zod](#zod) オブジェクトの `purchaseOrderValidator` にあり、実行時チェックと対応する TypeScript の型の両方を生成します。HTML フォームの値は `zod-form-data`（`zfd`）で整えられます。たとえば `zfd.text()` は、空のフィールドの `""` を `undefined` に変換します。ブラウザーとサーバーはこの 1 つのスキーマを共有するため、同じルールを適用します。

#### 4. ルート

`apps/erp/app/routes/x+/purchase-order+/new.tsx:30`

```ts theme={null}
export async function action({ request }: ActionFunctionArgs) {
  assertIsPost(request);
  const { client, companyId, companyGroupId, userId } =
    await requirePermissions(request, {
      create: "purchasing",
      bypassRls: true
    });

  const formData = await request.formData();
  const validation = await validator(newPurchaseOrderValidator).validate(
    formData
  );
  if (validation.error) return validationError(validation.error);
  /* ... check that the supplier and location belong to the company ... */
  const result = await insertPurchaseOrder(client, {
    ...validation.data,
    companyId,
    companyGroupId,
    createdBy: userId,
    customFields: setCustomFields(formData)
  });
  /* ... flash an error and redirect, or redirect to the new order ... */
}
```

Alforse の書き込みは、すべて次の流れで行われます。

> **1. メソッドを検証する、2. 権限を確認する、3. 入力を検証する、4. サービスを呼び出す、5. リダイレクトする。**

`apps/erp/app/routes/` 配下のルートファイルは、このフローに関わる 3 つをエクスポートできます。

| エクスポート | 役割 | 実行される場所 |
| - | - | - |
| [`loader`](#loader) | ページに必要なデータを取得する | サーバーのみ |
| [`action`](#action) | 送信されたフォームを処理する | サーバーのみ |
| `export default` | ページの React コンポーネント | サーバー、その後ブラウザー |

`loader` も `action` もブラウザーのバンドルには含まれないため、どちらもサーバー専用のコードを使えます。

#### 5. サービス関数

`apps/erp/app/modules/purchasing/purchasing.service.ts:1663`（`insertPurchaseOrder`）

データアクセスはサービス関数に委ねられ、ルートでは行いません。各関数は第 1 引数としてクライアントを受け取り、`supabase-js` でおなじみの `{ data, error }` の形で結果を返します。このクライアントは `getAlforseServiceRole()`（`packages/web/auth/src/lib/supabase/client.server.ts:13`）で作られます。名前は従来のものですが、実際にはこのリクエストでサインインしているユーザーとして `/api/web` のネイティブゲートウェイと通信します。クライアントは 1 つのリクエストに属し、他のリクエストからは使えません。

```ts theme={null}
const seq = await client.rpc("get_next_sequence", {
  sequence_name: "purchaseOrder",
  company_id: input.companyId
});

const order = await client
  .from("purchaseOrder")
  .insert({
    purchaseOrderId,
    supplierId: input.supplierId,
    /* ... */
  });
```

サービスはこの後、注文の納品行と支払行を保存します。これらのクライアント呼び出しは、それぞれ別のリクエストとしてゲートウェイに届きます。一連の書き込みを成功か失敗かのどちらかにまとめる必要がある場合、ゲートウェイはそれをネイティブオーナーにルーティングし、オーナーが操作全体を 1 つのデータベーストランザクション内で実行します（[データの書き込み](#writing-data)を参照）。

#### 6. データベースでの採番

`client.rpc("get_next_sequence", ...)` の呼び出しは、採番モジュール `modules/master-document-numbering/` が応答する [RPC](#rpc) です。このモジュールは送信と同じトランザクション内で次のカウンター値を確保し、会社の接頭辞、桁埋め、日付トークンを適用します。形式は[採番](https://docs.alforse.com/ja/docs/reference/sequences.md)で説明しています。

#### 7. 画面に戻る

最後に、アクションは `throw redirect(...)` を実行します。React Router は遷移先を読み込み、その `loader` がゲートウェイ経由で最新の状態を取得してページが描画されます。これとは別に、GET 以外の送信のたびに、React Router は現在のページの loader を自動的に再実行します。

***

## レイヤーごとの解説

### ブラウザーとルートファイル

2 つの画面アプリケーションはどちらも、フレームワークモードの **React Router v7** 上に構築され、サーバー上で React を描画します。これにより、1 つのルートファイルがデータ読み込みと UI をまとめて持てます。ERP と MES の画面には Remix も Next.js も使いません。ビルドは [Vite](#vite)、スタイリングは Tailwind v4 が担います。異なるのは `apps/backend` だけで、これは Next.js のアプリケーションホストです。

#### ルートはファイルです

`apps/erp/app/routes/` は 1,600 を超えるファイルがあり、URL 空間にそのまま対応します。`remix-flat-routes`（`apps/erp/app/routes.ts` で設定）のおかげで、フォルダー構成が URL 構成そのものになります。

| パターン | 例 | URL への影響 |
| - | - | - |
| `x+/` のように `+/` で終わるフォルダー | `x+/purchase-order+/new.tsx` | フォルダーと URL セグメントを兼ねる: `/x/purchase-order/new` |
| `_public+/` | `_public+/` | なし。フォルダーはファイルをまとめるだけで、セグメントは**追加されません** |
| `_layout.tsx` | `_layout.tsx` | なし。同じフォルダー内のすべてのルートを包みます |
| `$` 接頭辞 | `$orderId.tsx` | パラメーター: `/:orderId` |
| 名前の途中の `.` | `dimensions.new.tsx` | `/` になる: `dimensions/new` |
| `_index.tsx` | `_index.tsx` | フォルダー自体の URL のページ |
| `.server.ts` サフィックス | `*.server.ts` | なし。ルーターは無視し、ヘルパーはブラウザーに配信されません |

最上位のフォルダーは、利用者ごとにアプリを分けています。

| フォルダー | 内容 |
| - | - |
| `x+` | 認証済みのアプリケーションルート |
| `api+` | JSON エンドポイント |
| `_public+` | 認証ルート |
| `share+` | トークンで認証される公開リンク |
| `select-company+`, `onboarding+` | 会社の選択と初回セットアップ |

#### データの読み取り

ページはサーバー上の `loader` からデータを受け取ります。loader はサービス関数を呼び出して JSON を返し、コンポーネントは `useLoaderData<typeof loader>()` で受け取ります。すべてのページが必要とするデータ（ユーザー、会社、権限、機能フラグ）は、`apps/erp/app/routes/x+/_layout.tsx` が一度だけ読み込みます。コンポーネントは `apps/erp/app/hooks/` のフック（`useUser()`、`usePermissions()`、`useCompanySettings()` など）経由でこれにアクセスします。

#### UI からのデータの書き込み

フォームはそのルートの `action` に POST され、[追跡した書き込み](#tracing-one-write-end-to-end)と同じ流れになります。行の並べ替え、インライン編集、削除など、ユーザーがページにとどまるべき場合は、代わりに `useFetcher()` で送信します。それでも、その後ページは再検証されます。

#### 共有 UI

新しいコンポーネントを書く前に、次の場所を確認してください。

| 場所 | 内容 |
| - | - |
| `packages/web/react/` | `@alforse/web-react`、デザインシステム。ボタン、モーダル、コンボボックス、メニュー、レイアウトの基本部品。 |
| `apps/erp/app/components/` | アプリケーション固有のコンポーネント。中でも `components/Table/Table.tsx` は、すべての一覧画面が使います。 |

#### ブラウザーの状態

ブラウザーが保持する状態はごくわずかです。至る所で使う参照リスト（アイテム、得意先、仕入先、人員）は `apps/erp/app/stores/` 配下のストアに保持され、`RealtimeDataProvider` が更新します。使い捨ての UI 状態は [zustand](#zustand) に置きます。ユーザーとその権限の情報源は、常にルートの loader です。

#### 翻訳

テキストの翻訳には [Lingui](#lingui) を使います。文字列を `` t`...` `` または `<Trans>` で囲み、`pnpm lingui:compile` を実行してカタログをビルドします。

#### MES

MES は同じスタックを共有しますが、構造はより平坦です。`modules/` フォルダーはなく、サービスは `apps/mes/app/services/` にあり、約 100 のルートが 1 つの階層に並び、UI は手袋をした手でタブレットを操作することを前提としたサイズです。

### 認証と認可

`requirePermissions` を経由しないサーバー側のエントリポイントはありません。これが認証と認可の両方を処理します。

```ts theme={null}
const { client, companyId, userId } = await requirePermissions(request, {
  create: "purchasing",
});
```

定義は `packages/web/auth/src/services/auth.server.ts:152` にあります。呼び出しは 4 つの段階を経ます。

1. **呼び出し元を識別する。** ブラウザーのリクエストはネイティブのセッション Cookie を持ち、公開 API のリクエストは `alforse-key` ヘッダーを持ちます。キーの場合、ネイティブのキーオーナーがルートの要求する権限に照らしてキーを検証し、レート制限を適用します。詳細は [API キー](https://docs.alforse.com/ja/docs/building/api-keys.md)にあります。
2. **判断を求める。** ルートが必要とする権限（`view`、`create`、`update`、`delete`、場合によっては `role` から選択）は、バックエンドのネイティブ認可オーナーに送られます。オーナーは、選択された会社でのユーザーの付与に基づいて判断します。付与は、従業員タイプ、ユーザーのグループ、直接割り当ての 3 つの源から加算されます。
3. **許可されないものを断る。** 有効なセッションがない場合（なし、または期限切れ）、セッションは破棄され、ユーザーはログインに移動します。権限がない場合は、**Access Denied** メッセージ付きのリダイレクトになります。判断がセッションの保持するものと異なるユーザーまたは会社に対して返された場合、リクエストは ID の不整合として拒否されます。
4. **コンテキストを返す。** 成功すると、ルートはこのリクエストにスコープされたクライアントと、`companyId`、`companyGroupId`、`userId` を受け取ります。

#### マルチテナンシーと行レベルセキュリティ

多数の会社が 1 つのデータベースを共有するため、Alforse は**[マルチテナント](#multi-tenancy)**です。どの会社がアクティブかはセッションで決まり、URL は関与しません。

テナントのテーブルでは**[行レベルセキュリティ（RLS）](#rls)**が有効化され、強制されています。ベースラインのマイグレーション `packages/database/migrations/0001_baseline.sql` が `ALTER TABLE ... FORCE ROW LEVEL SECURITY` でこれを行います。各トランザクションの開始時に、現在の会社と操作者がトランザクションローカルの設定として保存され、すべてのポリシーはこれらの値に照らして行を検査します。別の会社のレコードを要求しても、結果は空です。

#### プラットフォームとワークスペースは別です

ワークスペースのライフサイクル、プラン、権利は、プラットフォームのコントロールプレーンが管理します。これは各顧客のワークスペースとは別に保たれたセキュリティドメインです。プラットフォーム側の権限は、顧客の業務レコードを読む暗黙の権利を与えません。

#### その他の種類のユーザー

ポータルを使う顧客と仕入先には、より限定された権限が与えられ、どのルートも `{ role: "employee" }` を必須にできます。`share+/` 配下のルートはログインを完全に不要とし、URL に含まれる推測不能なトークンでアクセスが決まります。

### データの書き込み

ERP と MES の画面は、当初書かれた時のデータアクセススタイルを保っています。バックエンドはデータベース接続を渡す代わりに、各スタイルをネイティブオーナーに変換します。経路は 4 つあります。

#### 1. ゲートウェイクライアント

| 呼び出し | エンドポイント | 解決先 |
| - | - | - |
| `from(table).select` | `/api/web/rest/v1/[relation]` | ネイティブの読み取りリレーション |
| `from(table).insert/update/delete` | `/api/web/rest/v1/[relation]` | そのデータを所有する製品またはモジュール。コマンドを検証し、`packages/database/src/` のストア経由で書き込みます。 |
| `rpc(name, args)` | `/api/web/rest/v1/rpc/[name]` | その RPC に対応付けられたネイティブオーナー |

#### 2. 名前付きネイティブ操作

多くの画面は、`packages/web/database/src/client.ts` の `getPostgresClient` を通じて、[Kysely](#kysely) でクエリを記述します。ビルダーは SQL を出力しません。クエリの形を記録し、`packages/web/database/native-operations/` のレジストリ（`purchasing.ts`、`inventory.ts`、`production-*`、`quality.ts` などのファイルがあります）にある名前付き操作とちょうど 1 つ照合します。どれにも一致しないクエリは拒否され、拒否にはそのクエリが示されます。ドラッグによる並べ替え（`apps/erp/app/modules/shared/sort-order.ts`）のような複数行の書き込みは、この仕組みによって全部成功するか全部失敗するかが保証されます。

#### 3. ネイティブオーナーと作業単位

出荷や請求書の転記、材料の払出し、生産数量の完了など、実質的な業務の書き込みは `products/*` と `packages/database/src/` のクラスとして実装されています。それぞれが**作業単位**の中で実行されます。つまり、業務トランザクション全体を通して 1 つの Postgres 接続を使い、操作者と会社が設定されます。本体で何か失敗すれば、すべてがロールバックされ、書き込み成功後に実行されるべきフックは、コミットの後で初めて発火します。

#### 4. RPC

最も狭い経路は、`client.rpc("name", args)` で呼び出す名前付き関数です。[追跡した書き込み](#tracing-one-write-end-to-end)の `get_next_sequence` がその一例です。各 RPC には対応するネイティブオーナーがあるか、明示的な未マッピング RPC の一覧に載っています。

計画は独立した領域です。[MRP](#mrp) と生産スケジューリングは、計画オーナーとともに `packages/web/planning` に実装されています。

### データベース

データベースは PostgreSQL で、前段には何もありません。PostgREST も Supabase プロジェクトも Edge Functions もありません。バックエンドがデータベースの唯一のクライアントであり、専用のロールで接続します。具体的には、RLS に拘束されるランタイムロールと、スケジューラーおよび配信ワーカー用の別個のロールです。

#### マイグレーション

各**[マイグレーション](#migration)**は、`packages/database/migrations/` にある番号付きの SQL ファイルです。

| ルール | 詳細 |
| - | - |
| ベースライン | `0001_baseline.sql` は変更されません。新規データベースの完全なスキーマと参照行を含みます。 |
| 以降の変更 | 変更ごとに新しいファイルを 1 つ、`0002` から番号を振り、`<ordinal>_<description>.sql` と命名します。 |
| 順序 | 数値順。各ファイルは、ランナーが所有するトランザクション内で実行されます。 |
| 不変性 | 一度適用したファイルは、編集、名前変更、並べ替え、削除を一切行いません。これを強制するため、ランナーは各ファイルのチェックサムを保存します。 |

ルールは `packages/database/migrations/README.md` に記載されており、ランナーは `packages/database/src/migrations.ts` です。

#### スキーマとテーブル

ベースラインからおよそ 740 のテーブルが作られ、2 つのスキーマに分かれています。`platform` はコントロールプレーンとネイティブオーナーのために、`web` は画面が読み取る契約スキーマです。読み取り側は[ビュー](#view)が提供します。

#### 識別子

ID は Postgres ではなくアプリケーションが生成し、`{prefix}_{base58(uuidv7)}` の形式で、`text` の主キーに保存されます。すべてのレコードタイプは、他のタイプが使わない接頭辞を持ちます。結果は作成日時順に並び、ログの 1 行や URL でも読み取れます。

#### 読み取りと書き込み

ルールは**オーナー経由で書き込み、リレーションから読み取る**です。書き込みはソースデータだけを保存します。合計、名前、サムネイルなど、画面がその上に表示するものはすべて、読み取りリレーションが導出します。

#### ファイル

アップロードはオブジェクトストレージに書き込まれ、画面は `/api/web/storage` ルート経由でこれにアクセスします。3 種類のプロバイダーをサポートします。Cloudflare R2、S3 互換ストア、顧客のストレージゲートウェイです（`apps/backend/src/production/blob-storage-provider.ts` とその周辺のファイルを参照）。会社ごとに独自のキー名前空間とクォータがあり、バックアップ用のコピープロファイルを追加できます。

***

## 書き込みの後: イベントとバックグラウンド処理

書き込みを行うコードは、その後続の処理までは実行しません。代わりにオーナーは、業務上の変更を行うトランザクションの内側で、ドメインイベントを**トランザクショナルアウトボックス**（`platform."outbox"`）に追加します。そのトランザクションがロールバックされればイベントも消え、コミットされればイベントの存在が保証されます。

```mermaid theme={null}
flowchart TD
  W[Business write] -->|same transaction| O[(Outbox row)]
  O -->|after commit| D[Delivery worker]
  O -->|after commit| R[Native runner]
  D --> N[Notification inbox, email, webhooks]
  R --> T[Queued tasks and scheduled tasks]
```

### 配信の保証

配信は少なくとも 1 回（at-least-once）であるため、ハンドラーは冪等になるように書かれています。一時的な失敗は、試行回数の上限と上限付き指数バックオフを持つポリシーで再試行され、恒久的な失敗は停止します。`kernel/events/` には、イベントバス、その再試行ポリシー、Webhook 配信があります。

### ネイティブランナー

バックグラウンドタスクは、バックエンドプロセス自体の一部であるランナー（`apps/backend/src/runner/`）が実行します。すべてのタスクは 4 つの段階を経ます。

1. **受付。** 会社、タスク、冪等キーを指定した行が `platform."backgroundTaskRun"` に追加されます。
2. **確保。** ランナーが行のリースを取得します。
3. **実行。** そのタスクに登録されたハンドラーが、会社に紐づくトランザクション内で実行されます。
4. **記録。** 実行は最終的に `pending`、`running`、`retrying`、`succeeded`、`deadLettered` のいずれかになります。

失敗した試行は、上限まで再試行されます。冪等キーは再試行やリースの引き継ぎを通じて変わらないため、ハンドラーは自分の作業がすでに完了していることを判断できます。画面は今もジョブを従来の名前で参照しており、`alforse/company-export` のようなタスク名と `packages/web/jobs` パッケージがそれを保っていますが、その背後に外部のジョブサービスはありません。

### スケジュール済みの処理

`apps/backend/src/runner/native-runner-schedules.ts` がスケジュールを宣言します。cron 式は UTC として読まれ、各ティックはすべてのアクティブな会社に展開されます。

| 頻度 | ジョブ |
| - | - |
| 15 分ごと | MRP、通知ダイジェスト |
| 毎時 | レンタル課金、契約時間課金 |
| 毎日 00:00 | 為替レートの更新 |
| 毎日 01:00 | 夜間の再計画 |
| 毎日 02:00 | 監査ログのアーカイブ |
| 毎日 04:00 | ワークフロー実行の保持 |

配信には独自のスケジュールがあります。通知リレーは毎分、メンテナンスの期日ディスパッチは 2 分ごとに実行され、そのほかにブロブのクリーンアップと新しく追加されたファイルのバックアップ用のジョブがあります。

### 通知

通知はユーザーごとの受信トレイに届きます。バックエンドは変更フィードを公開します（たとえばプライベートトピック `user:<userId>:notification`）。フィードは内容を含めずに、どの行が変わったかを ERP に伝え、ERP が必要なものを取得します。メールは、構成されているトランザクションメールのトランスポートを通じて送信されます。

***

## その他のエンジン

### ワークフロー

ワークフローは、顧客がドラッグ＆ドロップのキャンバス上で自分で設計する自動化です。ランナーが標準で持つタスクと混同しないでください。

| 観点 | ワークフロー | 組み込みタスク |
| - | - | - |
| 作成者 | 顧客（キャンバスを使用） | Alforse のエンジニア（TypeScript） |
| 保存形式 | バージョン付きのワークフロー定義 | ソースコード |
| 変更方法 | 顧客が編集するたび | デプロイ時のみ |

定義は `packages/web/workflows` と `kernel/workflows` のコードで記述・検証されます。各実行と、その中の各ステップは、後で調べられるようエンジンが記録します。ランナーの 2 つのスケジュールがこれを支えます。10 分ごとのワークフローのバックストップと、実行履歴の保持です。機能自体は[ワークフロー](https://docs.alforse.com/ja/docs/reference/workflows.md)で説明しています。

### CAD と組立計画

ここでは 2 つの処理が行われます。STEP 形式の CAD ファイルを、ブラウザーで表示できる 3D モデルに変換することと、組立指示の元になる、衝突を避けた分解順序を計算することです。コードはバックエンドの `apps/backend/src/cad/` にあります。`occt-import-js`（WebAssembly 向けにビルドされた OpenCASCADE）がジオメトリを解析し、モーションプランナーはその隣にあります。

どちらのアプリケーションも `/api/web/assembler` でこれを呼び出し、ベアラーキーで認証し、出力は `packages/web/viewer` で表示します。時間のかかる変換はランナーのタスクとして実行され、呼び出し側のページは完了を待つかポーリングします。

***

## 用語集

このページからリンクされている用語の短い定義です。A から Z の順です。

### Action

フォーム送信を処理する、ルートファイルのエクスポート。サーバー上で実行され、ブラウザーには配信されません。

### Kysely

TypeScript の SQL クエリビルダー。ここでは操作を記述するだけで、バックエンドがその記述を名前付きネイティブ操作に変換します。

### Lingui

翻訳に使うライブラリ。コード内でマークした文字列が言語ごとのファイルに抽出され、翻訳者が埋めます。

### Loader

ページが表示するデータを集める、ルートファイルのエクスポート。action と同様にサーバー側専用です。

### Migration

1 つのスキーマ変更を保持する、番号付きの SQL ファイル 1 つ。ベースラインとその後のファイルを順に合わせたものがスキーマそのものです。

### Monorepo

複数のアプリケーションと共有ライブラリを、まとめてバージョン管理・出荷する 1 つの Git リポジトリ。

### MRP

Material Requirements Planning（資材所要量計画）の略。需要と手持在庫から、何をいつまでに購入または製造するかを決めること。

### Multi-tenancy

独立した多数の顧客会社を 1 つのデータベースと 1 つの稼働アプリケーションで処理しつつ、各社には自社のデータだけを見せること。

### Postgres

PostgreSQL。Alforse の基盤となるリレーショナルデータベース。

### RLS

Row Level Security（行レベルセキュリティ）。Postgres がテーブルごとのルールを各クエリに自動で付与し、このルールが会社間でデータを見られないようにします。

### RPC

このページでは、アプリケーションコードから `client.rpc("name", args)` で名前付き関数を呼び出すこと。バックエンドがネイティブオーナーにルーティングします。

### Turborepo

どのパッケージがどれに依存するかを追跡してビルドを調整し、`pnpm build` が変更された部分だけをやり直すようにします。

### View

テーブルのように読み取れるが、書き込みはできない保存済みクエリ。

### Vite

ERP と MES をビルドし、開発中に配信します。

### zod

データの形を一度定義すれば、そこから検証と TypeScript の型の両方を導出できます。

### zustand

React 向けの最小限の状態管理ライブラリ。ここでは、モーダルが表示されているかなど、使い捨ての UI 状態に限定して使います。

***

## 関連

**Webhook**

イベントが発生した時点で、自社のエンドポイントに送信させます。

[Open](https://docs.alforse.com/ja/docs/building/webhooks.md)

**API キー**

プログラムに、公開 API 用の独自の認証情報を与えます。

[Open](https://docs.alforse.com/ja/docs/building/api-keys.md)

**採番**

`get_next_sequence` が文書番号を整形するしくみです。

[Open](https://docs.alforse.com/ja/docs/reference/sequences.md)

**バックアップ**

会社のデータとファイルのスナップショットを取得・復元します。

[Open](https://docs.alforse.com/ja/docs/platform/backups.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.