データモデル設計
本ドキュメントは概念データモデルを基に、論理・物理設計の詳細を定義する。概念データモデルがエンティティのビジネス上の意味と関係性を定義するのに対し、本ドキュメントではカラム定義・型・制約・設計パターンなどの実装に必要な情報を扱う。
設計指針
論理削除と状態遷移の使い分け
TASHIKAでは、エンティティの「非表示化」に2つのパターンを使い分ける。
| パターン | 適用エンティティ | 状態カラム | 用途 |
|---|---|---|---|
| 状態遷移 | Department, Employee | Status (active/closed), EmploymentStatus (active/on_leave/retired) | ビジネス上のライフサイクルを持ち、状態に業務的な意味があるエンティティ |
| 論理削除 | その他の多くのエンティティ | IsDeleted, DeletedAt, DeletedBy | ライフサイクルが単純で、誤登録の取消が主なユースケースのエンティティ |
背景: なぜ IsDeleted では不十分か
一般的なWebアプリケーションでは「論理削除」(IsDeleted フラグ)を用いてレコードを非表示にすることが多い。しかし、部署と従業員に関しては IsDeleted では業務の実態を正しくモデリングできない。
- 状態に意味がある: 部署の「廃止」と「削除」は異なる概念。廃止された部署は後継部署への参照を持ち、過去データの文脈として参照される。「削除された」のではなく「役目を終えた」のである
- 遷移が一方向ではない: 従業員は
active → on_leave → activeのように状態を往復する。IsDeleted の true/false では「休職中」を表現できない - クエリの意図が明確になる:
WHERE EmploymentStatus = 'active'は業務上の意図を直接表現するが、WHERE IsDeleted = falseは「削除されていない」という否定形であり、休職者を含むのか含まないのかが曖昧 - 付随情報を持てる:
ClosedAt、SuccessorDepartmentId(部署)やRetiredAt、LeaveReason(従業員)など、状態遷移に伴うコンテキスト情報を自然に保持できる
状態遷移パターンを選択する基準
- 「削除」ではなく「廃止」「退職」のようにドメイン固有の終了概念がある
- 状態の往復がある(例: active ⇔ on_leave)
- 状態遷移に付随情報がある(例: ClosedAt, SuccessorDepartmentId, RetiredAt, LeaveReason)
- クエリの意図を明確にしたい(
WHERE Status = 'active'vsWHERE IsDeleted = false)
IsDeleted を使うべきケース
すべてのエンティティで状態遷移モデルが適切なわけではない。以下のようなケースでは従来の IsDeleted が適切:
- 誤登録の取消が主なユースケース(例: 保険控除データの入力ミスを「削除」する)
- ライフサイクルが単純で、存在するか・しないかの2値で十分な場合
- 状態遷移に業務上の意味がない場合
→ ドメイン上の組織ライフサイクルについては 従業員の雇用状態 を参照
NOT NULL デフォルト原則
カラムは原則 NOT NULL 制約を付与する。NULL は「値が存在しないことに明確な意味がある」場合のみ許容し、その理由をコメントで明記する(例: FK による任意のリンク、論理削除の DeletedAt)。
監査フィールド
全エンティティに以下の監査フィールドを付与:
CreatedAt(timestamptz, NOT NULL)CreatedBy(uuid, NOT NULL)UpdatedAt(timestamptz, NOT NULL)UpdatedBy(uuid, NOT NULL)
テナント分離モデル
アクセス制御の階層
| レベル | フィルタ条件 | 適用ロール |
|---|---|---|
| Agency | AgencyId による絞り込み | agency_admin, agency_staff |
| Tenant | TenantId による絞り込み(RLS強制) | tenant_admin, employee |
| Employee | EmployeeId による絞り込み | employee(自分のデータのみ) |
| System | フィルタなし(全テナント) | sys_admin, sys_support(マスキング付き) |
マイナンバーの保護設計
- マイナンバーはアプリケーションレベルで暗号化してからDBに保存
- 平文のマイナンバーがログ・エラーメッセージ・デバッグ出力に含まれないことをコードレビューで検証
- 検索が必要な場面(照合等)は下4桁のハッシュ値で絞り込み後、復号して照合
- → CR-009、SR-004 参照
エンティティ詳細設計
組織領域
DM-203 Employee
DepartmentId(uuid, FK → Department, nullable: 部署未割当の従業員を許容)EmploymentStatus(text, NOT NULL):active/on_leave/retiredRetiredAt(timestamptz, nullable: 退職日)LeaveStartedAt(timestamptz, nullable: 休職開始日)LeaveReason(text, nullable: 休職理由)- → 従業員の雇用状態
退職時の領域ごとのデータ取り扱い:
| 領域 | 退職時の対応 |
|---|---|
| 組織 | 所属解除(DepartmentId を NULL に更新)。当時の部署名は申告・給与計算側のスナップショットが保持 |
| 家族 | 退職後に不要。申告側のトランザクションデータがスナップショットを保持 |
| アイデンティティ | 即時無効化(認証不可) |
| 申告・給与・国税・地方税 | 税法上の保存義務(7年)に基づき保持。保存義務期間満了後に消去 |
→ ドメイン上の根拠は 退職に伴う法的要件と情報の性質 を参照
DM-204 Department
Code(text, NOT NULL): テナント内一意の識別コード。CSV/APIインポート時のマッチングキーName(text, NOT NULL): 部署名Path(ltree, NOT NULL): PostgreSQL ltree 型の階層パスParentDepartmentId(uuid, FK → Department, nullable: ルート部署)SortOrder(integer, NOT NULL): 同階層内の表示順Status(text, NOT NULL):active/closedClosedAt(timestamptz, nullable: 廃止日時)ClosedBy(uuid, nullable: 廃止操作者)SuccessorDepartmentId(uuid, FK → Department, nullable: 後継部署)
IsDeleted は使わない。Status で状態管理する。→ 上記「論理削除と状態遷移の使い分け」を参照
組織改編(廃止・統合・名称変更)は Status、ClosedAt、ClosedBy、SuccessorDepartmentId で実現する。→ ビジネスルールは 組織構造: 組織改編パターン を参照
DM-205 AdminDepartmentScope
UserId(uuid, FK → User, NOT NULL)DepartmentId(uuid, FK → Department, NOT NULL)IncludeDescendants(boolean, NOT NULL): 配下部署を含むかどうか
スコープ未設定の tenant_admin は全部署にアクセス可能(後方互換性)。→ SR-004 DepartmentScopePolicy
家族領域
DM-301 FamilyMember
- マイナンバー(暗号化保存:
encrypted_mynumber bytea+mynumber_last4 text) - 非居住者区分コード:
00=居住者、01=非居住者30歳未満/70歳以上、02=非居住者留学、03=非居住者障害者、04=非居住者38万円以上送金 - 障害者区分: なし/一般の障害者/特別障害者/同居特別障害者
- 同居の有無(誰と同居しているかを記録: 同居老親等と同居特別障害者で定義が異なる)
- 合計所得金額(見積額)
- 勤労学生フラグ
- 留学フラグ(
IsStudyingAbroad) - 年間送金額は RemittanceRecord (DM-303) の合計から算出
- 年齢は生年月日から 12/31 現況で自動計算(前日満了ルール適用: 1/1 生まれは 12/31 に加齢)
- → 親族のカテゴリ
DM-302 FamilyRelationship
属性定義:
RelationshipType(text): 血族/姻族/配偶者LineType(text): 直系/傍系GenerationType(text): 尊属/卑属/同世代Degree(integer): 親等数(血族1〜6、姻族1〜3)Relationship(text): 具体的な続柄SeikeiWoItsuNiSuru(boolean): 生計一フラグ
これらの属性から親族区分判定関数(Is血族、Is姻族、Is直系、Is傍系、Is尊属、Is卑属、Is直系尊属、Is直系卑属1親等、Is親族範囲内)を構成し、税法カテゴリ判定(第2層)の入力とする。民法の定義に基づくため年度パラメータに依存しない。→ 親族のカテゴリ
DM-303 RemittanceRecord
RemittanceDate(date): 送金日(銀行送金=送金実行日、クレジットカード=カード利用日)RemittanceMethod(text):bank_transfer/credit_card/electronic_payment(R6改正で電子決済手段追加)CurrencyCode(text): ISO 4217通貨コード。JPYの場合は外貨換算不要ForeignAmount(decimal, nullable): 外貨建て金額。JPY送金の場合はnullConversionMethod(text):ttm/actual_jpy/batchの3方式- 原則: 送金日のTTM
- 例外①: 円預金→外貨→即時送金の実支出額(
actual_jpy) - 例外②: 年最後の送金日のTTMで一括換算(
batch)
TtmRate(decimal, nullable): 適用したTTMレート(ConversionMethodがttm/batchの場合)JpyAmount(decimal, NOT NULL): 邦貨換算後の円額IncludesFees(boolean): 手数料を送金額に含むかFeeAmount(decimal, nullable): 手数料額EvidenceId(uuid, FK → UploadedEvidence, nullable): 送金関係書類の証憑
→ 非居住者親族
DM-304 FamilyInputInvitation
TokenHash(text, NOT NULL): HMAC-SHA256署名付きトークンのハッシュ値(平文トークンはDBに保存しない)Status(text, NOT NULL):pending/accessed/completed/expired/revokedPreferredLocale(text): 家族の優先言語(ja/en/zh/ko/vi/pt/es)ExpiresAt(timestamptz, NOT NULL): 有効期限(発行から7日)MaxAccessCount(integer, NOT NULL, default: 5): 最大アクセス回数CurrentAccessCount(integer, NOT NULL, default: 0): 現在のアクセス回数VerificationFailCount(integer, NOT NULL, default: 0): 本人確認失敗回数(3回でURL無効化)PendingInputData(jsonb, nullable): 家族が入力した未承認データInputCompletedAt(timestamptz, nullable): 入力完了日時ReviewedAt(timestamptz, nullable): 従業員が確認・承認した日時ReviewResult(text, nullable):accepted/rejected
申告領域
DM-401 Declaration
- Draft 状態では
DeclarationSnapshot(JSONB) に自動保存し、Submitted 遷移時に正規化テーブルへ展開する - Fact/Judgment 分離原則: 事実データは控除可否に関係なく常に保持し、控除判定結果は Tax Calculation Core の出力として別管理する
DM-406 DeclarationSnapshot
- JSONB 形式で申告データ全体をスナップショットとして保存
- 履歴復元の単位として機能
部署参照のスナップショット設計:
スナップショット取得時点の部署名を JSONB 内に含める。これにより:
- 組織改編で部署名が変わっても、過去の申告データは当時の部署名で表示される
- 部署が廃止されても、過去の帳票・レポートに影響しない
- 部署ID(FK)はリアルタイムの参照として Employee に持ち、スナップショットには部署名(文字列)を記録する
DM-408 ElectronicCertificate
e-Tax XML 形式の証明書:
- TEG800: 生命保険料控除証明書
- TEG810: 地震保険料控除証明書
- TEG840: 国民年金保険料等控除証明書
- TEG850: 小規模企業共済等掛金控除証明書
パース結果を保持。XML 原本は UploadedEvidence (DM-407) に保存し、本エンティティではフィールドマッピング済みの構造化データを保持する。証明書のユニークコード(重複インポート防止用)、証明年分、発行者情報、証明額(実績)と見込額(12月期想定)の二重構造を管理。
DM-409 CategoryJudgmentResult
税法カテゴリ判定の中間結果。Declaration と FamilyMember の間に位置し、各家族構成員に対する税法上のカテゴリ該当有無を boolean フラグ群で保持する:
- 同一生計配偶者
- 控除対象配偶者
- 源泉控除対象配偶者
- 扶養親族
- 控除対象扶養親族(一般/特定/老人/同居老親等)
- 16歳未満の扶養親族
- 特定親族
判定理由を記録。Tax Calculation Core の CategoryJudgment 層が生成し、DeductionCalculation 層への入力となる。年度パラメータ(閾値 T、年齢境界等)を適用した結果であるため、前年から引き継いではならない。→ 設計思想: 税法カテゴリ判定の一元化
DM-410 CalculationDiff
再計算前後の TaxCalculationResult インスタンスを比較して生成される差分データ。Tax Calculation Core 内の純粋関数 CalculationDiffGenerator.Generate(before, after) が生成する。
3層構造:
- パイプライン最終結果の差分(PreviousYeaAnnualTax / CurrentYeaAnnualTax / TaxDifference、PreviousOverUnderPayment / CurrentOverUnderPayment)
- 控除の変動リスト(DeductionChange: 控除種類コード→DM-803、変更前後の金額、変動種類 Appeared=新規適用 / AmountChanged=金額変動 / Disappeared=適用解除)
- カテゴリ判定の変動リスト(CategoryChange: FamilyMemberId、カテゴリ名、before/after の boolean 値)
DB には永続化せず、API レスポンスとして都度生成する。→ バックエンドアーキテクチャ: CalculationDiff
給与領域
DM-501 TaxCalculationResult
11ステップパイプライン(①給与等の総額→②給与所得控除後の金額→③所得金額調整控除額→④調整控除後の給与所得→⑤所得控除の合計額→⑥課税給与所得金額→⑦算出所得税額→⑧住宅借入金等特別控除額→⑨年調所得税額→⑩年調年税額→⑪過不足額)の各段階の値を保持:
- 適用された控除一覧(→ DM-502 DeductionDetail で内訳を保持)
- 税法カテゴリ判定結果(→ DM-409 CategoryJudgmentResult)
- 端数処理情報(切捨て/切上げの各ステップ)
Version(integer): 1=初回確定、2以降=再年調PreviousResultId(uuid, FK → TaxCalculationResult, nullable)AdjustmentReason(text, nullable): 訂正理由DifferenceAmount(decimal): 差額
Finalized 後は不変。→ 年税額計算
DM-502 DeductionDetail
控除ごとの計算過程を保持する TaxCalculationResult の内訳エンティティ(1:N):
- 控除種類(→ DM-803 DeductionTypeMaster 参照)
- 適用/非適用の判定結果
- 適用理由(どの税法カテゴリに基づくか)
- 計算式の各項(入力値、閾値、率)
- 算出された控除額
- 端数処理の適用有無
対象控除:
- 人的控除: 基礎・配偶者・扶養・障害者・寡婦・ひとり親・勤労学生・特定親族特別
- 物的控除: 社会保険料・小規模企業共済等掛金・生命保険料・地震保険料
- 調整: 所得金額調整控除・住宅借入金等特別控除
「なぜその控除が適用/非適用なのか」のトレーサビリティを確保する。
DM-503 WithholdingSlip
TEG109 フォーマット(令和7年以降)の全29フィールド:
- 支払金額、給与所得控除後の金額、所得控除の額の合計額、源泉徴収税額
- 配偶者関連(控除対象配偶者の有無等、配偶者(特別)控除の額)
- 扶養親族等の数(特定/老人/その他/特親)
- 16歳未満扶養親族の数、障害者の数、非居住者である親族の数
- 特定親族特別控除の額(R7新設)
- 保険料控除関連
- 住宅借入金等特別控除(最大2件内訳)
- 基礎控除の額、所得金額調整控除額
- 摘要欄(構造化データとして保持し出力時にテキスト整形)
3系統の区分コード体系:
- 非居住者親族分類コード(00〜04)
- 特定親族特別控除の額の区分コード(10〜91、居住者/非居住者別)
- 住宅借入金等特別控除区分コード(住/認/増/震+取得区分)
出力先(税務署提出用/受給者交付用/給与支払報告書)によるマイナンバー記載の差異を吸収する。→ 給与所得の源泉徴収票、e-Tax XML構造設計書
アイデンティティ領域
DM-602 RefreshToken
- トークンハッシュ値(平文トークンはDBに保存しない)
- 有効期限(7日)
- 発行デバイス情報
- 失効フラグ
設定・マスタ系
DM-802 TaxYearMaster
年度別の税制パラメータ:
- 算出所得税額の速算表: 税率区分と控除額(5%/10%/20%/23%/33%/40%)
- 給与所得控除テーブル: 収入区分別の控除額計算式、最低保障額(R6以前55万円/R7以降65万円、上限195万円)
- 公的年金等控除の速算表
- 各控除の閾値・上限額:
- 共通閾値 T: R7=58万円 / R8予定=62万円
- 配偶者特別控除の段階的逓減テーブル
- 特定親族特別控除の段階的逓減テーブル
- 基礎控除の所得連動テーブル
- 年齢境界パラメータ: 16歳/19歳/23歳/70歳の扶養区分境界
- 復興特別所得税率: 2.1%(H25〜R19)
- 使用する源泉徴収票の様式ID(TEG104〜TEG109)
- 施行日パラメータ(R7の12/1施行のような年度途中施行への対応)
DM-803 DeductionTypeMaster
控除種類マスタ。年末調整で扱う全控除を定義:
人的控除(Exemption)9種: 基礎控除、配偶者控除、配偶者特別控除、扶養控除、特定親族特別控除(R7新設)、障害者控除、寡婦控除、ひとり親控除、勤労学生控除
物的控除(Deduction)4種: 社会保険料控除、小規模企業共済等掛金控除、生命保険料控除、地震保険料控除
調整2種: 所得金額調整控除(給与所得の計算上の調整)、住宅借入金等特別控除(税額控除)
各控除について:
- 控除コード
- 日本語名、英語表記(NTA公式)
- 申告要否区分(自動適用/要申告/要証明書)
- 適用開始年度、適用終了年度(廃止された控除の管理)
- 年度別パラメータ(TaxYearMaster への参照)
→ 控除
DM-804 IncomeTypeMaster
所得税法で定める10種類の所得分類:
| 所得種類 | 英語キー | 特記事項 |
|---|---|---|
| 給与所得 | KyuyoShotoku | 速算表方式 |
| 公的年金等の雑所得 | KotekiNenkinToZatsuShotoku | 速算表方式 |
| 退職所得 | TaishokuShotoku | 分離課税 |
| 事業所得 | JigyoShotoku | 損益通算可 |
| 不動産所得 | FudosanShotoku | 損益通算可 |
| 雑所得-業務 | GyomuZatsuShotoku | |
| 雑所得-その他 | SonotaZatsuShotoku | |
| 配当所得 | HaitoShotoku | 1/2算入あり |
| 譲渡所得 | JotoShotoku | 1/2算入あり |
| 一時所得 | IchijiShotoku | 1/2算入あり |
| 利子所得 | RishiShotoku | 源泉分離 |
| 山林所得 | SanrinShotoku | 分離課税 |
各所得種類について、計算方式、課税方式(総合課税/源泉分離/申告分離)、勤労/不労の区分、合計所得金額への算入方法、損益通算可否、年末調整での重要度を保持。→ 所得
DM-805 BrandingConfiguration
AgencyId(uuid, FK → Agency, nullable) — 排他的に一方のみTenantId(uuid, FK → Tenant, nullable) — 排他的に一方のみdisplay_name(text): プロダクト表示名称logo_header_url/logo_login_url/logo_email_url: 用途別ロゴURLfavicon_url(text)color_primary/color_secondary/color_accent: テーマカラーfooter_text(text): メールフッター用テキストis_override_allowed(boolean, default: false): Agency設定の場合、配下テナントによる上書き許可フラグ
設定変更時はバージョンを採番し変更履歴を保持。
ブランド解決順序: Tenant固有設定 → Agency設定 → システムデフォルト(TASHIKA標準)。
DM-806 CustomDomain
domain_name(text, NOT NULL): FQDNverification_status(text):pending/verified/failed/expiredverification_token(text): DNS TXTレコード検証用トークンdns_cname_target(text): CNAME設定先ssl_status(text):provisioning/active/expiredverified_at(timestamptz, nullable)ssl_expires_at(timestamptz, nullable)email_from_address(text, nullable): カスタム送信元メールアドレスemail_reply_to(text, nullable)spf_verified/dkim_verified/dmarc_verified(boolean): 送信ドメイン認証ステータス
SSL/TLS証明書はGoogle-managed certificatesで自動管理。→ SR-011
監査・運用系
DM-902 Notification
保持期間の二層構造:
- 配信メタデータ(送信日時、宛先ハッシュ、テンプレートID、配信ステータス、チャネル種別)= 7年(監査目的、→ CR-010 準拠)
- メール本文・通知本文 = 90日後に削除(個人情報最小化・プライバシー保護)
SendGrid Event Webhook からの配信結果(delivered/bounce/dropped/spam_report 等)を受信して配信ステータスを更新する。
DM-904 NotificationTemplate
- テンプレートID、テンプレート名
- チャネル種別(Email/Slack/Teams/LINE WORKS/システム内通知)
- 件名テンプレート、本文テンプレート(Scriban / Liquid 互換形式)
- 多言語バリアント(従業員ポータル向けは7言語対応)
- テナント別カスタマイズ(デフォルトテンプレートをテナント単位で上書き可能)
- 有効/無効フラグ
- バージョン管理(更新履歴)
言語選択は受信者の preferred_locale に基づき、フォールバック順序は ja → en → テンプレートのデフォルト言語。→ 非同期ジョブ・通知アーキテクチャ設計
エンティティ名マッピング
ドメイン概念(日本語)とコード実装時のエンティティ名(英語)の対応表。
| 日本語(ドメイン用語) | 英語(エンティティ名) | 用語集ID |
|---|---|---|
| 代行事業者 | Agency | GL-015 |
| 代行スタッフ | AgencyStaff | GL-020 |
| 企業 | Company | GL-016 |
| 事業所 | Establishment | GL-021 |
| 部署 | Department | GL-022 |
| 従業員 | Employee | GL-017 |
| 雇用状態 | EmploymentStatus | GL-023 |
| 申告 | Declaration | GL-005 |
| 申告スナップショット | DeclarationSnapshot | GL-006 |
| 家族構成員 | FamilyMember | — |
| 親族関係 | FamilyRelationship | — |
| 源泉徴収票 | WithholdingSlip | — |
| 保険料控除 | InsuranceDeduction | — |
| 税額計算エンジン | TaxCalculationCore | — |
| 基礎控除 | BasicExemption | GL-067 |
| 配偶者控除 | SpouseExemption | GL-068 |
| 配偶者特別控除 | SpecialSpouseExemption | GL-069 |
| 扶養控除 | DependentExemption | GL-070 |
| 特定親族特別控除 | SpecialExemptionForSpecifiedRelatives | GL-071 |
| 障害者控除 | DisabledPersonExemption | GL-072 |
| 寡婦控除 | WidowExemption | GL-073 |
| ひとり親控除 | SingleParentExemption | GL-074 |
| 勤労学生控除 | WorkingStudentExemption | GL-075 |
| 所得金額調整控除 | IncomeAdjustmentExemption | GL-076 |
| 生命保険料控除 | LifeInsurancePremiumDeduction | GL-077 |
| 地震保険料控除 | EarthquakeInsurancePremiumDeduction | GL-078 |
| 社会保険料控除 | SocialInsurancePremiumDeduction | GL-079 |
| 小規模企業共済等掛金控除 | SmallBusinessMutualAidDeduction | GL-080 |
| 同一生計配偶者 | SpouseLivingInSameHousehold | GL-081 |
| 控除対象配偶者 | SpouseQualifiedForDeduction | GL-082 |
| 源泉控除対象配偶者 | SpouseQualifiedForWithholdingDeduction | GL-083 |
| 扶養親族 | DependentRelative | GL-035 |
| 控除対象扶養親族 | DependentRelativeQualifiedForDeduction | GL-036 |
| 特定扶養親族 | SpecifiedDependentRelative | GL-037 |
| 老人扶養親族 | ElderlyDependentRelative | GL-038 |
| 同居老親等 | ElderlyParentLivingTogether | GL-039 |
| 年少扶養親族 | DependentRelativeUnder16 | — |
| 特定親族 | SpecifiedRelative | GL-040 |