Skip to content

Latest commit

 

History

History
182 lines (144 loc) · 19.7 KB

File metadata and controls

182 lines (144 loc) · 19.7 KB
title ddl — スキーマ定義
status current
scope 会計コア
audience
開発
updated 2026-09-20
supersedes
related
../Project.md
../../docs/10_会計ドメイン設計.md
../../docs/12_マスタ台帳.md
../../docs/decisions/0020-スキーマは現在形の正典で持ち変更は差分で配る.md

ddl — スキーマ定義

会計コアのテーブル定義。新規 DB を作るときは番号順に sql CLI で流す(自前で DB 接続しない)。 既にある DB へは直接流さず、マイグレーション(下記)で配る。

pwsh -NoProfile -File tools/clb/sql.ps1 -File Designer/ddl/001_organization.sql

tools/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 種類。

  1. スキーマの形 — 主キー・日付列の宣言型・金額の型・論理削除列の不在・他部品のテーブルを参照しないこと
  2. 制約の実効 — 改ざん・重複・FK 違反などの書き込みが実際に拒まれること
  3. 初期データDesigner/seed/ が制約に反しないこと、設計上の約束が守られていること
  4. 区分値の一致 — 同じ区分値が 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_numberapp_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 が DateOnlyMM/dd/yyyy 文字列にして比較し、年跨ぎの範囲検索が壊れる)
  • 金額は INTEGER(整数円)。REAL を使わない

この DDL に固有の方針

検証を DB にも二重に置く

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-03E-PERIOD-ORPHAN
I-04 締め済み期間に計上できない I-04
I-05 計上済み仕訳は変更も削除もされない journal_entries / journal_linesBEFORE UPDATE / BEFORE DELETE トリガ(明細を計上済みの伝票へ付け替える UPDATE も止めるNEW 側を見るトリガが要る。qa/03 L-25) I-05
I-06 訂正・取消は原仕訳を持つ CHECK(自己参照の禁止も) I-06
I-13 損益科目の明細には部門がある — (科目区分が要る) I-13
I-14 外部伝票の二重計上を防ぐ idempotency_keyUNIQUE — (フェーズ 6 の投入 API)
I-17 伝票番号を再利用しない UNIQUE (fiscal_year_id, entry_no) + 採番表 + 下書きに番号を持たせない CHECK I-17E-FISCAL-YEAR
入力年月日は変えられない BEFORE UPDATE トリガ — (サーバが値を決める)
取消は原仕訳 1 本につき 1 本 部分 UNIQUE インデックス E-REVERSAL-DUPLICATE
再計上は原仕訳 1 本につき 1 本 部分 UNIQUE インデックス E-CORRECTION-DUPLICATE
再計上より先に取消がある — (順序は行をまたぐ) E-CORRECTION-ORIGINAL-LIVEE-CORRECTION-BEFORE-REVERSAL
金額は正の整数円 CHECK (amount > 0) E-AMOUNT
行番号は正の整数で伝票内に一意 CHECK (line_no > 0)UNIQUE E-LINE-NO
消費税行だけが親行を持つ CHECK E-TAX-PARENTE-TAX-INHERIT
部門「全社共通」は 1 件だけ 部分 UNIQUE インデックス
摘要のない仕訳は計上できない(10 §4-2-1 BEFORE UPDATE のトリガ(下書き → 計上のときだけ鳴る。空白だけも空とみなす) E-DESCRIPTION-EMPTY
補助科目は 2 値(15 §1ADR-0038 §3 BEFORE UPDATE のトリガ(下書き → 計上のときだけ鳴る。外すのは計上済みの原仕訳を写した取消だけ——関門より外す範囲が小さい。理由は 005_journals.sql の注記) E-SUBACCOUNT-REQUIREDE-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-1ADR-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 列。012013docs/12 §2-2docs/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)。