この記事の要点
- 相続税申告期限である「相続開始を知った翌日から10か月」を基準点とし、逆算した中間タスクの期日を自動算出するモデルを設計した。
- 申告期限から逆算する主要3タスク(概況把握・専門家相談・財産評価)の相対日数を管理し、土日祝日判定を含む期日導出ロジックを実装した。
- Cloudflare D1上に相対オフセットを持つマスタテーブルと案件ごとの期日スナップショットを分離保持し、期限変更時の再計算整合性を担保した。
環境・前提条件
- Node.js 20.12.0
- TypeScript 5.4.3
- Cloudflare Workers (wrangler 3.50.0)
- Cloudflare D1
- date-fns 3.6.0
相続開始日から申告期限日を厳密に計算するには?
国税通則法第10条第1項(初日不算入・起算日に応当する日の前日に満了)に従って、知った日と同じ日付の10か月後の日を満了日とし、休日(土日祝日・12月29日〜1月3日)に当たる場合は翌日へ繰り下げます。
相続税の申告および納付の期限は、相続税法第27条第1項により「相続の開始があったことを知った日の翌日から10か月以内」と規定されています(参照:国税庁 No.4205 相続税の申告と納税)。国税通則法第10条第1項により、期間の初日は算入せず、月で定めた期間は起算日(知った日の翌日)に応当する日の前日に満了するため、満了日は知った日と同じ日付の10か月後の日になります(例:1月6日に知った場合は11月6日)。満了日が日曜日・国民の祝日・土曜日・12月29日〜31日、または年始の1月2日・3日に当たるときは、同条第2項などにより、その翌日が申告期限になります(参照:e-Gov法令検索 国税通則法・国税庁 国税通則法基本通達 第10条関係)。
import { addMonths, isWeekend, addDays } from "date-fns";
export function calculateFilingDeadline(knowingDate: Date): Date {
// 満了日=知った日と同じ日付の10か月後(翌日起算・応当日の前日に満了。国税通則法10条1項)
// date-fns の addMonths は、その月に同じ日が無いとき月末日にそろえる(法の扱いと同じ)
let deadline = addMonths(knowingDate, 10);
// 休日なら翌日へ繰り下げ(同条2項)。祝日は祝日テーブルとの照合を挟む想定
while (isWeekend(deadline) || isYearEndHoliday(deadline)) {
deadline = addDays(deadline, 1);
}
return deadline;
}
// 12月29日〜31日(施行令2条2項)と1月1日〜3日
function isYearEndHoliday(d: Date): boolean {
const m = d.getMonth() + 1;
const day = d.getDate();
return (m === 12 && day >= 29) || (m === 1 && day <= 3);
}
申告期限から逆算するタスクをどうデータモデル化するか?
申告期限日を絶対基準日(Day 0)と定義し、各タスクを「期限からのマイナス日数・月数」としてマスタ定義したうえで、案件ごとに確定日付をレコード生成します。
相続税申告における典型的な初動プロセスには、「遺産の概況把握」「税理士相談」「不動産評価」の3タスクが存在します。これらを期日管理システムで扱う場合、タスクマスタに offset_months や offset_days を負の整数で保持します。案件作成時に確定した申告期限へオフセットを適用し、タスクインスタンステーブルへ静的な期日として保存することで、マスタ変更が進行中案件に波及しない不変性を確保します。
-- タスク定義マスタ
CREATE TABLE IF NOT EXISTS task_templates (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
offset_months INTEGER NOT NULL, -- 基準日からの月数(例: -7)
display_order INTEGER NOT NULL
);
-- 案件別タスク期日
CREATE TABLE IF NOT EXISTS case_tasks (
id TEXT PRIMARY KEY,
case_id TEXT NOT NULL,
task_template_id TEXT NOT NULL,
due_date TEXT NOT NULL, -- ISO8601 (YYYY-MM-DD)
is_completed INTEGER DEFAULT 0 NOT NULL,
FOREIGN KEY (task_template_id) REFERENCES task_templates(id)
);
マスタには「遺産の概況把握(-7か月)」「税理士相談(-5か月)」「不動産評価(-3か月)」のように登録します。2月に相続開始を知った場合は申告期限が同年12月となるため、逆算によって5月に概況把握、7月に税理士相談、9月に不動産評価という期日導出が一意に確定します。
Cloudflare D1で期日整合性を保ちながら一括登録するには?
D1のバッチ実行API(db.batch)を使用し、基準日計算と全タスクの期日導出をトランザクション内で不可分に永続化します。
Cloudflare D1はSQLiteベースのサーバーレスデータベースであり、複数の更新文を配列として渡すバッチトランザクションをサポートしています。申告期限日の再計算が行われた際、派生する未完了タスクの期日を同一トランザクション内で更新することで、データ不整合を防ぎます。
export async function setupCaseTasks(
db: D1Database,
caseId: string,
knowingDate: Date
): Promise<void> {
const deadline = calculateFilingDeadline(knowingDate);
const templates = await db
.prepare("SELECT id, offset_months FROM task_templates ORDER BY display_order ASC")
.all<{ id: string; offset_months: number }>();
const statements: D1PreparedStatement[] = [];
for (const t of templates.results) {
const taskDueDate = addMonths(deadline, t.offset_months);
const dateString = taskDueDate.toISOString().split("T")[0];
statements.push(
db.prepare(
`INSERT INTO case_tasks (id, case_id, task_template_id, due_date)
VALUES (?1, ?2, ?3, ?4)
ON CONFLICT(id) DO UPDATE SET due_date = excluded.due_date`
).bind(`${caseId}:${t.id}`, caseId, t.id, dateString) // 案件×タスクで一意にし、再計算時は同じ行を更新する
);
}
await db.batch(statements);
}
Cloudflare Workersの実行環境ではCPU時間が制限されますが、date-fnsによる日付加減算とD1のプリペアドステートメント組み立ては、計算量が小さく負荷はわずかです。起算点の厳密な処理と逆算マスタの組み合わせにより、法改正や業務手順の見直し時にもマスタ値の調整のみでシステム全体の追随が可能になります。
この記事は株式会社HY(横浜・湘南/不動産・相続)の社内システム開発の記録です。