| title | ddl — スキーマ定義 | ||||
|---|---|---|---|---|---|
| status | current | ||||
| scope | 会計コア | ||||
| audience |
|
||||
| updated | 2026-09-20 | ||||
| supersedes | |||||
| related |
|
会計コアのテーブル定義。新規 DB を作るときは番号順に sql CLI で流す(自前で DB 接続しない)。
既にある DB へは直接流さず、マイグレーション(下記)で配る。
pwsh -NoProfile -File tools/clb/sql.ps1 -File Designer/ddl/001_organization.sqltools/clb/sql.ps1 がデザイナ exe のパスを ../LocalEnvironment.md(Git 追跡外)から解決し、
結果 JSON を標準出力に返す。一時ファイルを作らない(30 §8)。
スキーマを変えたらサーバとデザイナの再起動が要る(列定義が static にキャッシュされるため)。
本フォルダは現在形の正典であり、その場で書き換える
(ADR-0020)。
既存 DB への配布は ../migrations/ が担う。書き換えたら
同じ変更をマイグレーションとしても書き、tools/clb/migrate.ps1 -Apply → -Verify で稼働 DB に当てる。
マイグレーションの書き方の規約は migrations の README が持つ。
網は 2 つ: マイグレーションの書き忘れ・書き間違いは同値テスト(MigrationEquivalenceTests)が、
稼働 DB そのもののスキーマのずれはコミット前フックの -Verify が捕まえる。
BusinessApp.Schema.Tests がこの DDL ファイルそのものをインメモリ SQLite に適用して検査する。
テスト用に書き写したスキーマを使わないので、写し間違いも「直したつもり」も起きない。
dotnet test BusinessApp.slnx検査するのは 4 種類。
- スキーマの形 — 主キー・日付列の宣言型・金額の型・論理削除列の不在・他部品のテーブルを参照しないこと
- 制約の実効 — 改ざん・重複・FK 違反などの書き込みが実際に拒まれること
- 初期データ —
Designer/seed/が制約に反しないこと、設計上の約束が守られていること - 区分値の一致 — 同じ区分値が DDL の
CHECK・ CLB のデザイン enum ・ C# の列挙型の 3 か所で一致していること。どれか 1 つを直し忘れると、コンパイルも designcheck も通ったまま 「画面で選べるのに保存できない」という壊れ方をする
| # | ファイル | 内容 |
|---|---|---|
| 001 | 001_organization.sql |
事業所情報 |
| 002 | 002_periods.sql |
会計年度・会計期間 |
| 003 | 003_consumption_tax.sql |
税区分 |
| 004 | 004_masters.sql |
勘定科目・補助科目・部門・取引先(取引先部品のもの — ADR-0025) |
| 005 | 005_journals.sql |
仕訳・仕訳明細・伝票番号の採番(マスタではなくデータ) |
| 006 | 006_partner_registrations.sql |
適格請求書発行事業者の登録(有効期間つき。公表情報の写し。取引先部品のもの) |
| 007 | 007_auth.sql |
利用者アカウント(認証部品のテーブル。本体は CLB のもので、役割の列だけを間借りする——ADR-0032) |
| 008 | 008_master_code_format.sql |
マスタのコードの書式(6 つの表に同じ規則。docs/12 §2-1・ADR-0047)。1 ファイルにまとめてあるのはトリガの作られる順のため——表の定義の隣に置くと、既存 DB へ配る側で 005 のトリガより後になり、正典と順が食い違う |
| 009 | 009_partner_meaning.sql |
使用中の取引先はコードを変えられない(会計コアの 4 マスタと揃える。ADR-0047 の決定 9)。取引先だけは伝票(journal_entries.partner_id)も見る——明細が空なら伝票の値が実効値になるから |
| 010 | 010_natural_key_text.sql |
自然キーになる列に BLOB を入れさせない(partners.corporate_number・app_users.user_name)。BLOB は TEXT の列にそのまま残り、'admin' とぶつからない——008 でコードの 6 表を塞いだのと同じ穴 |
| 011 | 011_date_format.sql |
日付の列は年月日として読める値だけを受け取る(5 表 12 列)。1 ファイルにまとめてあるのは 008 と同じくトリガの作られる順のため——fiscal_years は 008 にトリガを持つので 002 の末尾には置けない |
| 012 | 012_text_length.sql |
マスタと取引先の文字の欄に上限を置く(5 表 8 列。docs/12 §2-2)。1 ファイルにまとめてあるのは 008・011 と同じくトリガの作られる順のため。数えられない値(BLOB・NUL を含む TEXT・壊れた UTF-8)は長さより先に断る |
| 013 | 013_journal_text_length.sql |
伝票の摘要と明細の内容に上限を置く(2 表 2 列。docs/10 §4-2-1)。012 と条件の骨格はまったく同じで、FieldLengthConsistencyTests が 20 本を突き合わせる。別のファイルにしてあるのは決めた文書が違うからである |
各テーブルの扱い(所有・誰が編集するか・版と削除)は docs/12_マスタ台帳 が持つ。
適用順は外部キーの向きで決まっている。税区分(003)を勘定科目(004)より先に作るのは、 勘定科目が既定税区分を参照するためである(逆向きの参照は無い)。
ClaudeCodeForDesigner/Docs/DatabaseGuidelines.md に従う。とくに次を外さない。
- 主キーは
id INTEGER PRIMARY KEY AUTOINCREMENT、外部キーは{単数形}_id INTEGER - テーブル・列は snake_case 英語
- 日付列を
TEXTで宣言しない。DATE/DATETIME/TIMEで宣言する (TEXTだと CLB がDateOnlyをMM/dd/yyyy文字列にして比較し、年跨ぎの範囲検索が壊れる) - 金額は
INTEGER(整数円)。REALを使わない
AccountingCore の検証(ADR-0008)が
第一の関門だが、CSV 取込・API・スクリプト・手作業の SQL のどれからでも通る最後の関門として
CHECK 制約・UNIQUE 制約・トリガを置く。
ADR-0004 の
「規則を迂回する経路を作らない」を、規約ではなく DB に守らせる。
二重防御は「片方を緩めても、もう片方が残る」ことに意味がある。 逆に片方だけ厳しくすると気づけない (C# だけ厳しくすると CSV 取込が抜け、DB だけ厳しくすると画面で通って保存で落ちる)ので、 両側を 1 つの表で並べる。この表そのものを機械で突き合わせることは採らない——表は文書であって定義ではなく、読む形を作っても壊れたときに気づけない。 行ごとの列集合の一致は、定義(トリガの定義文・デザイン JSON・C# の定数)どうしをテストで突き合わせられるものだけ守る (20 §4 の表。読めなかったら落ちる表明を必ず持つ)。
| 不変条件 | DB 側の担保 | C# 側の担保 |
|---|---|---|
| I-01 伝票単位で貸借一致 | — (行をまたぐので CHECK では書けない) |
I-01 |
| I-03 有効な会計期間に属する | — | I-03 + E-PERIOD-ORPHAN |
| I-04 締め済み期間に計上できない | — | I-04 |
| I-05 計上済み仕訳は変更も削除もされない | journal_entries / journal_lines の BEFORE UPDATE / BEFORE DELETE トリガ(明細を計上済みの伝票へ付け替える UPDATE も止める。NEW 側を見るトリガが要る。qa/03 L-25) |
I-05 |
| I-06 訂正・取消は原仕訳を持つ | CHECK(自己参照の禁止も) |
I-06 |
| I-13 損益科目の明細には部門がある | — (科目区分が要る) | I-13 |
| I-14 外部伝票の二重計上を防ぐ | idempotency_key の UNIQUE |
— (フェーズ 6 の投入 API) |
| I-17 伝票番号を再利用しない | UNIQUE (fiscal_year_id, entry_no) + 採番表 + 下書きに番号を持たせない CHECK |
I-17 + E-FISCAL-YEAR |
| 入力年月日は変えられない | BEFORE UPDATE トリガ |
— (サーバが値を決める) |
| 取消は原仕訳 1 本につき 1 本 | 部分 UNIQUE インデックス | E-REVERSAL-DUPLICATE |
| 再計上は原仕訳 1 本につき 1 本 | 部分 UNIQUE インデックス | E-CORRECTION-DUPLICATE |
| 再計上より先に取消がある | — (順序は行をまたぐ) | E-CORRECTION-ORIGINAL-LIVE + E-CORRECTION-BEFORE-REVERSAL |
| 金額は正の整数円 | CHECK (amount > 0) |
E-AMOUNT |
| 行番号は正の整数で伝票内に一意 | CHECK (line_no > 0) + UNIQUE |
E-LINE-NO |
| 消費税行だけが親行を持つ | CHECK |
E-TAX-PARENT + E-TAX-INHERIT |
| 部門「全社共通」は 1 件だけ | 部分 UNIQUE インデックス | — |
| 摘要のない仕訳は計上できない(10 §4-2-1) | BEFORE UPDATE のトリガ(下書き → 計上のときだけ鳴る。空白だけも空とみなす) |
E-DESCRIPTION-EMPTY |
| 補助科目は 2 値(15 §1・ADR-0038 §3) | BEFORE UPDATE のトリガ(下書き → 計上のときだけ鳴る。外すのは計上済みの原仕訳を写した取消だけ——関門より外す範囲が小さい。理由は 005_journals.sql の注記) |
E-SUBACCOUNT-REQUIRED + E-SUBACCOUNT-NOT-ALLOWED |
| 取引先を要する科目の明細には取引先がある(15 §1-2) | BEFORE UPDATE のトリガ(下書き → 計上のときだけ鳴る。実効値——明細が空なら伝票の取引先を見る。「無い」は NULL だけでなく「マスタに実在しない」まで——外部キーを切った経路が守る相手だから。外すのは計上済みの原仕訳を写した取消だけ——補助科目の 2 値と同じ形) |
E-PARTNER-REQUIRED(実在と有効は 2026-09-10 から関門も見る(15 §1-3)) |
| 補助科目が明細の勘定科目に属する(15 §1) | 無い。関門だけが見ている——取込・CLI・SQL の直打ちからは素通りする(塞ぐのは取込と投入 API を作る回。04 §3 のフェーズ 6) | E-SUBACCOUNT-MISMATCH |
| 使用中のマスタは意味を変えられない(ADR-0038) | 5 マスタの BEFORE UPDATE OF <意味を決める列> トリガ + REPLACE で id を乗っ取る経路を止める BEFORE INSERT / BEFORE UPDATE OF id トリガ |
MasterMeaningGate(取引先は 2026-09-09 に足した。ADR-0047 の決定 9。数える単位は振替伝票の枚数) |
| 使用中の科目で「取引先を要する」をオフにできない(15 §1-2。一方通行。オンはいつでも通る。使用中になるまでは働かない——残余は 15 §1-2) | BEFORE UPDATE OF requires_partner のトリガ(OLD = 1 AND NEW = 0 のときだけ鳴る) |
MasterMeaningGate の一方通行の列 |
| マスタのコードの書式(docs/12 §2-1・ADR-0047) | 6 表の BEFORE INSERT / BEFORE UPDATE OF code トリガ(CHECK ではない——後から足すには表の作り直しが要る)。大小を無視した重複は UNIQUE ... COLLATE NOCASE の索引 |
MasterCode(前後の空白を落とすのはこちらだけ。トリガは落とした後の姿を見る) |
| 単一法人(ADR-0005) | CHECK (id = 1) |
— |
| 日付の列は年月日として読める値だけを受け取る(5 表 12 列。011) | BEFORE INSERT / BEFORE UPDATE OF <列> のトリガ(CHECK ではない——後から足すには表の作り直しが要る)。NULL は通す |
DbValue.ParseDateTime(読み出しの側。5 書式の TryParseExact)。受理する集合は両側で同じでなければならない——DB が広いと、読んだ瞬間に落ちる行が作れる(DateFormatGuardTests が毎回突き合わせる) |
| 文字の欄の上限(7 表 10 列。012・013。docs/12 §2-2・docs/10 §4-2-1) | BEFORE INSERT / BEFORE UPDATE OF <列> のトリガ。LENGTH() は符号点で数えるので、数えられない値は別の文言で断る——BLOB(typeof)・NUL を含む TEXT(instr(CAST(x AS BLOB), x'00'))・壊れた UTF-8(バイト数 > 4 × 符号点)。NUL をバイト数の比で探そうとして穴が開いた(qa/03 の L-48)。NULL は通す |
MasterTextLength(マスタと取引先)と JournalLineRules(伝票)——関門が本体。string.Length ではなく符号点で数えて DB と揃え、U+0000 も同じように断る。定数を 2 つに分けてあるのは、決めた文書も動く理由も違うからである。上限の数は C#・DDL・デザインの 3 か所にあり、FieldLengthConsistencyTests が毎回突き合わせる |
摘要の「空白」の範囲を、関門とトリガで同じにしてある理由は
10 §4-2-1 が持つ(両方向の同値は JournalDescriptionGuardTests が見る)。
トリガ・インデックスは、このファイルの末尾に足す——マイグレーションは既存 DB の末尾に作るので、 途中に置くと同じ表のトリガの作られた順が正典と食い違う(理由と守り方は migrations/README)。 表が既に後ろの番号のファイルにトリガを持っているときは、この規則が「その表の定義のファイル」を禁じる側に働く ——そのときは 008・011 のように、規則ごと後ろの番号のファイルにまとめる。
行をまたぐ判定・マスタを引く判定は DB の CHECK では書けないので、JournalEntryValidator だけが担保する。
弥生会計も会計期間(年度)単位で採番しており、番号の付け方を強制する法令は無い (市販ソフト比較)。
弥生会計は再付番できるが、本プロジェクトは作らない。 伝票番号は帳簿間の相互関連性 (電帳規則 5 ⑤一ロ)を担保する一連番号なので、後から振り直すと出力済みの帳簿との対応が切れる。 欠番があること自体は問題にならない。
creator / updater は CLB の予約名で、値は認証部品の利用者識別子だが、
app_users への外部キーは張らない。
利用者アカウントは会計コアの責務ではなく別部品のものであり(ADR-0029 §6)、 DB 制約で結ぶと会計コアが認証部品なしでは立ち上がらなくなる。 C# のモジュール依存(ADR-0050 §8)と 同じ規律を DB にも当て、スキーマテストが「他部品のテーブルを参照していないこと」を検査する。
CLB の予約名 LogicalDelete はどのテーブルにも置かない。
- 仕訳は計上したら消せない(I-05)。論理削除の列があること自体が誤った経路になる
- マスタは削除ではなく無効化する(
is_activeの意味も含めて docs/12_マスタ台帳 が持つ)
(取引先の登録テーブルまわりの未確認は 14_適格請求書発行事業者の登録 の保留リストが持つ)
- 2026-09-14 同時実行の検体が 1 本も無い。
ux_journal_entries_single_reversalなどの部分一意索引は 「同時に 2 人が取り消すと、両方が『まだ取り消されていない』を読む」ために置いてあるのに、 その筋書きを再現するテストが無い(いまの検体は 1 接続の逐次実行)。 道具は既にある——TestDatabase.Connect(existing)(同じインメモリ DB への 2 本目の接続)は呼び出し元が 0 件である。 制約ノックアウトでは原理的に見えない(逐次の検体でも同じ索引に当たる)。フェーズ 3 の着手前
他の制約に包まれた冗長な制約が 7 本あり、制約ノックアウトでは永久に殺せない
(ADR-0053。上限の正典は tools/clb/knockout.ps1)。
保留にしない——**6 本(マスタのコードの列の UNIQUE)**は
008 が「外すには表の作り直しが要り、得るのは重複した制約 1 本の削除だけ」として
残すと決めてある。
7 本目は partner_invoice_registrations の表の UNIQUE (partner_id, registration_no, valid_from) で、
011 が入って日付が ISO に揃った日に包まれた——それまでは日付として読めない値だけが
この UNIQUE に届いていた(qa/02 のラウンド 89)。