Tax Calculation Core(税計算コア)
税額計算ロジックは、Webフレームワークから完全に分離した 純粋なクラスライブラリ として実装する。ASP.NET Core、EF Core、データベースへの一切の依存を持たず、単体でテスト可能なプロジェクトとして構成する。なお、税計算ロジックはTASHIKAの競争優位性の源泉であり、NuGetパッケージ等での外部配布は行わない。
設計原則
| 原則 | 説明 |
|---|---|
| Calculation Core as Library | Web API や DB に一切依存せず、単体でテスト可能。外部配布は行わない |
| 純粋関数 (Pure Functions) | 状態変異を排除し、同一入力に対して常に同一結果を保証 |
| イミュータブル (Immutability) | 入出力のデータ構造はすべて不変(record / readonly) |
| 年度別バージョニング | 税制改正に対応するため、年度ごとに計算ロジックをバージョン管理 |
| Fact/Judgment 分離 | 事実データ(属性)と控除判定結果を分離し、判定は常に計算で導出する |
→ 満たす要件: CR-005 データの不変性と再現性
計算処理の実行方針: バックエンド集約
税計算・控除判定のロジックは バックエンド(Tax Calculation Core)にのみ実装し、フロントエンドでは一切行わない。
| 観点 | バックエンド集約(採用) | フロント+バック二重実装 |
|---|---|---|
| Single Source of Truth | C# の1実装のみ。国税庁計算例との照合もここで保証 | C# と TypeScript の2実装を常に同期する必要がある |
| 税制改正の対応コスト | 1箇所の修正で完了 | 2言語で改正を同時反映。端数処理(切捨て/切上げ混在)の乖離リスクが高い |
| ロジックの秘匿性 | サーバー内に閉じる | JS バンドルに閾値・判定条件が含まれる |
| 検証の信頼性 | サーバーが唯一の権威 | フロント計算は参考値にしかならず、二重実装のコストに見合わない |
パフォーマンス: 純粋関数パイプラインの全体再計算は <1ms であり、API ラウンドトリップを含めてもユーザー体感上の問題にならない。Draft 中のリアルタイムプレビューは入力の debounce(300-500ms)+ TanStack Query のキャッシュで十分な応答性を確保できる。
フロントエンドの役割: 表示フォーマット(通貨書式、端数表示)と入力の即時フィードバック(形式バリデーション)に限定する。
ライブラリ構成
Tax Calculation Core は以下の3層で構成する。各層は名前空間で分離し、依存方向を厳密に制御する。
Tashika.TaxCalculation/
├── CategoryJudgment/ -- 第1層: 税法カテゴリ判定(事実 → カテゴリ該当有無)
├── DeductionCalculation/ -- 第2層: 控除計算(カテゴリ → 控除額)
├── Pipeline/ -- 第3層: 年税額計算パイプライン(11ステップ)
├── Parameters/ -- 年度パラメータ(TaxYearMaster 由来の値オブジェクト群)
├── Models/ -- 入出力モデル(イミュータブル record)
└── SpeedTables/ -- 速算表(給与所得控除、所得税率)11ステップ年税額計算パイプライン
年末調整の年税額(年調年税額)算出は、11ステップの段階的パイプラインとして実装する。各ステップは純粋関数であり、前ステップの出力を入力として受け取る。
→ 出典: 国税庁 年末調整のしかた(令和7年分)p37-39 → ドメイン知識の詳細: 年税額計算 → Golden Test データ: ゴールデンテスト
各ステップの詳細
| # | ステップ名 | 入力 | 出力 | 計算ロジック | 端数処理 |
|---|---|---|---|---|---|
| ① | 給与等の収入金額(総額) | PayrollImport[] | decimal GrossSalary | 1月〜12月の給与・賞与の合計 | なし |
| ② | 給与所得控除後の給与等の金額 | GrossSalary | decimal SalaryIncomeAfterDeduction | 給与所得控除速算表を適用(→ DM-802 TaxYearMaster) | 660万円以上の算式計算時: 1円未満 切捨て |
| ③ | 所得金額調整控除額 | GrossSalary, CategoryJudgmentResult | decimal IncomeAdjustmentDeduction | 収入850万円超 かつ 所定の要件に該当する場合: (min(GrossSalary, 1000万円) − 850万円) × 10% | 1円未満 切上げ |
| ④ | 調整控除後の給与所得金額 | ②, ③ | decimal AdjustedSalaryIncome | ② − ③(③の適用がない場合は②がそのまま④) | なし |
| ⑤ | 所得控除の合計額 | DeductionDetail[] | decimal TotalIncomeDeductions | 人的控除9種 + 物的控除4種 の合計(→ 控除計算層) | 生命保険料: 1円未満切上げ、地震保険料: 1円未満切上げ |
| ⑥ | 課税給与所得金額 | ④, ⑤ | decimal TaxableIncome | max(④ − ⑤, 0)、1,000円未満切捨て | 1,000円未満 切捨て |
| ⑦ | 算出所得税額 | ⑥ | decimal AssessedTax | 所得税速算表: ⑥ × 税率 − 控除額(→ DM-802 TaxYearMaster) | なし(速算表適用前に⑥が1,000円単位に丸め済み) |
| ⑧ | 住宅借入金等特別控除額 | HousingLoanDeduction | decimal HousingLoanCredit | 住宅借入金等特別控除申告書の提出がある場合に適用(税額控除) | 100円未満 切捨て |
| ⑨ | 年調所得税額 | ⑦, ⑧ | decimal YeaIncomeTax | max(⑦ − ⑧, 0)(マイナスにならない) | 0に切捨て(フロア) |
| ⑩ | 年調年税額 | ⑨ | decimal YeaAnnualTax | ⑨ × 102.1%(復興特別所得税)、100円未満切捨て | 100円未満 切捨て |
| ⑪ | 過不足額 | ⑩, decimal WithheldTotal | decimal OverUnderPayment | ⑩ − 源泉徴収済み税額の合計。プラス→不足額(追加徴収)、マイナス→過納額(還付) | なし |
パイプライン関数のシグネチャ(C#)
// 各ステップは純粋関数として実装。年度パラメータは TaxYearParameters に集約。
public static class TaxCalculationPipeline
{
// ② 給与所得控除後の金額(速算表適用)
public static decimal CalculateSalaryIncomeAfterDeductionAsync(
decimal grossSalary,
SalaryDeductionTable deductionTable);
// ③ 所得金額調整控除額
public static decimal CalculateIncomeAdjustmentDeduction(
decimal grossSalary,
CategoryJudgmentResult categories,
IncomeAdjustmentParameters parameters);
// ⑥ 課税給与所得金額(1,000円未満切捨て、≥0)
public static decimal CalculateTaxableIncome(
decimal adjustedSalaryIncome,
decimal totalIncomeDeductions);
// ⑦ 算出所得税額(速算表適用)
public static decimal CalculateAssessedTax(
decimal taxableIncome,
TaxRateTable taxRateTable);
// ⑨ 年調所得税額(≥0)
public static decimal CalculateYeaIncomeTax(
decimal assessedTax,
decimal housingLoanCredit);
// ⑩ 年調年税額(×102.1%、100円未満切捨て)
public static decimal CalculateYeaAnnualTax(
decimal yeaIncomeTax,
decimal reconstructionTaxRate);
// ⑪ 過不足額
public static decimal CalculateOverUnderPayment(
decimal yeaAnnualTax,
decimal withheldTotal);
// パイプライン全体を実行し TaxCalculationResult を生成
public static TaxCalculationResult ExecutePipeline(
TaxCalculationInput input,
TaxYearParameters parameters);
}算出所得税額の速算表(令和7年分)
速算表は年度パラメータ(→ DM-802 TaxYearMaster)として管理する。
| 課税給与所得金額 (A) | 税率 (B) | 控除額 (C) | 税額 = (A)×(B) − (C) |
|---|---|---|---|
| 1,950,000円以下 | 5% | 0円 | (A) × 5% |
| 1,950,000円超〜3,300,000円以下 | 10% | 97,500円 | (A) × 10% − 97,500 |
| 3,300,000円超〜6,950,000円以下 | 20% | 427,500円 | (A) × 20% − 427,500 |
| 6,950,000円超〜9,000,000円以下 | 23% | 636,000円 | (A) × 23% − 636,000 |
| 9,000,000円超〜18,000,000円以下 | 33% | 1,536,000円 | (A) × 33% − 1,536,000 |
| 18,000,000円超〜18,050,000円以下 | 40% | 2,796,000円 | (A) × 40% − 2,796,000 |
注意: 課税給与所得金額が18,050,000円を超える場合は年末調整の対象外。速算表の「控除額」は累進課税のギャップ調整値であり、所得控除とは無関係。
端数処理のまとめ
年末調整の計算過程では 切捨てと切上げが混在 するが、いずれも 納税者有利の方向 に丸められる設計意図がある。
| # | 対象 | 端数 | 処理 | パイプラインステップ |
|---|---|---|---|---|
| 1 | 給与所得控除後の金額(算式計算、660万円以上) | 1円未満 | 切捨て | ② |
| 2 | 所得金額調整控除額 | 1円未満 | 切上げ | ③ |
| 3 | 生命保険料控除額 | 1円未満 | 切上げ | ⑤ |
| 4 | 地震保険料控除額 | 1円未満 | 切上げ | ⑤ |
| 5 | 住宅借入金等特別控除額 | 100円未満 | 切捨て | ⑧ |
| 6 | 課税給与所得金額 | 1,000円未満 | 切捨て | ⑥ |
| 7 | 年調所得税額(控除しきれない部分) | — | 0にフロア | ⑨ |
| 8 | 年調年税額(×102.1%) | 100円未満 | 切捨て | ⑩ |
パターン: 控除額の計算は切上げ(納税者に有利)、課税所得・税額の計算は切捨て(同じく納税者に有利)。