控除計算層
控除計算層は、CategoryJudgment 層が生成した CategoryJudgmentResult(→ DM-409)を入力として受け取り、各控除の適用可否・控除額を算出する。控除ごとの計算過程は DeductionDetail(→ DM-502)として記録する。
→ 控除の分類と詳細: 控除 → 控除種類マスタ: DM-803 DeductionTypeMaster
控除の分類と計算ロジック概要
人的控除(Exemption)9種:
| # | 控除名 | 計算ロジック概要 | 入力 |
|---|---|---|---|
| 1 | 基礎控除 | 本人の合計所得金額に応じた段階テーブル(R7: 本則58万円+加算額、9区分) | 本人の合計所得金額 |
| 2 | 配偶者控除 | 本人の合計所得金額×配偶者年齢区分の2軸テーブル(3段階×2区分) | IsKoujoTaishoHaigusha, 本人の合計所得金額, 配偶者年齢 |
| 3 | 配偶者特別控除 | 配偶者の合計所得金額×本人の合計所得金額の2軸段階逓減テーブル(9段階×3段階) | IsHaigushaTokubetsuKoujoTaisho, 配偶者の合計所得金額, 本人の合計所得金額 |
| 4 | 扶養控除 | 年齢区分(一般38万円/特定63万円/老人48万円/同居老親等58万円) | IsKoujoTaishoFuyoShinzoku, 年齢区分フラグ群 |
| 5 | 特定親族特別控除 | 特定親族の合計所得金額に応じた段階逓減テーブル(9段階) | IsTokuteiShinzoku, 特定親族の合計所得金額 |
| 6 | 障害者控除 | 区分別定額(一般27万円/特別40万円/同居特別75万円) | DisabilityGrade |
| 7 | 寡婦控除 / ひとり親控除 | 要件判定後に定額(寡婦27万円/ひとり親35万円) | 本人属性(婚姻歴、子の有無、合計所得金額) |
| 8 | 勤労学生控除 | 要件判定後に定額(27万円) | 本人属性(学生、合計所得金額、勤労所得割合) |
物的控除(Deduction)4種:
| # | 控除名 | 計算ロジック概要 | 入力 |
|---|---|---|---|
| 9 | 社会保険料控除 | 支払額全額(給与天引分 + 本人申告分) | InsuranceDeduction |
| 10 | 小規模企業共済等掛金控除 | 支払額全額 | InsuranceDeduction |
| 11 | 生命保険料控除 | 3区分(一般/介護医療/個人年金)×新旧制度の計算式→合算上限12万円 | InsuranceDeduction |
| 12 | 地震保険料控除 | 地震保険+旧長期損害保険の計算式→合算上限5万円 | InsuranceDeduction |
調整2種:
| # | 控除名 | 計算ロジック概要 | パイプラインステップ |
|---|---|---|---|
| 13 | 所得金額調整控除 | 給与収入850万円超の場合、(min(収入,1000万円)−850万円)×10% | ③(所得控除ではなく給与所得の調整) |
| 14 | 住宅借入金等特別控除 | 税額控除(所得控除ではない) | ⑧ |
CategoryJudgmentResult → DeductionDetail への変換
csharp
// カテゴリ判定結果を一度だけ計算し、複数の控除計算関数で共有
var categories = judge.EvaluateAll(taxpayer, familyMembers);
// 各控除計算関数はカテゴリの bool フラグのみを参照(生の所得額・閾値を直接参照しない)
var deductions = new List<DeductionDetail>
{
BasicDeductionCalculator.Calculate(taxpayer.TotalIncome, parameters),
SpouseDeductionCalculator.Calculate(categories, taxpayer.TotalIncome, parameters),
DependentDeductionCalculator.Calculate(categories, parameters),
DisabilityDeductionCalculator.Calculate(categories),
SpecifiedRelativeDeductionCalculator.Calculate(categories, parameters),
// ... 物的控除
SocialInsuranceCalculator.Calculate(insuranceData),
LifeInsuranceCalculator.Calculate(insuranceData, categories, parameters),
EarthquakeInsuranceCalculator.Calculate(insuranceData, parameters),
};各 DeductionDetail(→ DM-502)には、控除種類(→ DM-803 DeductionTypeMaster)、適用/非適用の判定結果、適用理由(どの税法カテゴリに基づくか)、計算式の各項、算出された控除額、端数処理の適用有無を記録する。
変更影響表示(CalculationDiff)
入力内容の変更により再計算が行われた際、「何がどう変わったか」を利用者に伝えるための差分データを生成する。再計算前後の TaxCalculationResult インスタンスを比較する純粋関数として Tax Calculation Core 内に実装する。
→ データモデル: DM-410 CalculationDiff
設計方針
- インスタンス比較による差分生成: 依存グラフの追跡やイベント発行ではなく、イミュータブルレコード同士の構造比較で差分を検出する。純粋関数+イミュータブルレコードの設計により、再計算の前後で2つのインスタンスが自然に手元にある
- 3層の段階的開示: ユーザーの関心度に応じて情報量を制御する。全員が見るべき最終結果(Layer 1)から、専門家向けの判定変化(Layer 3)まで段階的に開示
- DB 非永続化: CalculationDiff は API レスポンスとして都度生成し、DB には保存しない。元となる TaxCalculationResult が保存されていれば、いつでも再生成可能
データ構造
csharp
// Tax Calculation Core 内の純粋関数
public static class CalculationDiffGenerator
{
public static CalculationDiff Generate(
TaxCalculationResult before,
TaxCalculationResult after);
}
// Layer 1: パイプライン最終結果の変動
public record CalculationDiff
{
public decimal PreviousYeaAnnualTax { get; init; }
public decimal CurrentYeaAnnualTax { get; init; }
public decimal TaxDifference { get; init; } // + = 増税, − = 減税
public decimal PreviousOverUnderPayment { get; init; }
public decimal CurrentOverUnderPayment { get; init; }
// Layer 2: 控除の変動
public IReadOnlyList<DeductionChange> DeductionChanges { get; init; }
// Layer 3: カテゴリ判定の変動
public IReadOnlyList<CategoryChange> CategoryChanges { get; init; }
public bool HasChanges => TaxDifference != 0;
}
// Layer 2: 控除の変動
public record DeductionChange
{
public string DeductionTypeCode { get; init; } // DM-803 の控除種類コード
public decimal PreviousAmount { get; init; }
public decimal CurrentAmount { get; init; }
public decimal Difference { get; init; }
public DeductionChangeKind Kind { get; init; }
}
public enum DeductionChangeKind
{
Appeared, // 非適用 → 適用
AmountChanged, // 適用のまま金額変動
Disappeared // 適用 → 非適用
}
// Layer 3: カテゴリ判定の変動
public record CategoryChange
{
public Guid FamilyMemberId { get; init; }
public string CategoryName { get; init; } // "控除対象配偶者" 等
public bool PreviousValue { get; init; }
public bool CurrentValue { get; init; } // true→false = 該当→非該当
}比較タイミングと before/after の対応
| 状態 | before | after | 用途 |
|---|---|---|---|
| Draft 中の編集 | 直前スナップショットの仮計算結果 | 編集後の再計算結果 | リアルタイムプレビュー |
| Draft → Submitted | 最新 Draft の仮計算結果 | Submitted 時の確定計算結果 | 提出前の最終確認 |
| Returned → 再 Draft | Returned 時点の計算結果 | 修正後の再計算結果 | 差し戻し修正の影響確認 |
| 再年調 | Finalized (v1) の TaxCalculationResult | 再計算 (v2) の TaxCalculationResult | 訂正による影響額の確認 |
ポータル別のデフォルト表示レベル
| ポータル | デフォルト表示 | 展開可能 |
|---|---|---|
| 従業員 | Layer 1(過不足額の変動) | Layer 2(控除の変動サマリ) |
| 企業管理者 | Layer 1 + Layer 2 | Layer 3(カテゴリ判定変化) |
| 代行事業者 | Layer 1 + Layer 2 + Layer 3 | 全展開 |
既存機能との関係
| 既存機能 | 比較軸 | CalculationDiff との関係 |
|---|---|---|
| 前年差分ハイライト | 年度間の**属性(事実)**の差分 | 別の仕組み。属性 vs 計算結果で対象が異なる |
| 控除対象外理由 | 現時点の判定理由(単一スナップショット) | Layer 3 が動的な before/after 版として補完 |
| 計算結果表示 | 現時点の計算結果(単一スナップショット) | Layer 1-2 が変動情報を追加 |
| 計算プロセス可視化 | 現時点のパイプライン(単一スナップショット) | パイプライン各ステップの before/after に拡張 |