2026年1月25日の開発日記
今日は計画ページの大幅リデザイン、GMO CSV仕訳インポートの設計、日付形式統一マイグレーションの完了など、UI改善とデータ整備の両面で進捗があった。
今日やったこと
1. 月次推移表リンク機能とinject問題の解決
月次推移表のファイル列から読取一覧ページへのリンク機能を実装した。
実装内容:
- ファイル列のセルをクリックすると読取一覧ページへ遷移
- 該当月・該当勘定科目でフィルタされた状態で表示
- URLクエリパラメータで状態を引き継ぎ
発生した問題: Vue inject警告
[Vue warn]: injection "Symbol(router)" not found.
ルーターのinjectがコンポーネント内で見つからないエラーが発生。
原因と解決:
// NG: setup外でuseRouterを呼び出し
const router = useRouter() // これがトップレベルだと問題
// OK: setup内またはonMounted内で呼び出し
const navigateToReceipts = () => {
const router = useRouter()
router.push({
path: '/receipts',
query: { year, month, accountCode }
})
}
Composables APIのuseRouterはsetupコンテキスト内で呼び出す必要がある。グローバルスコープで呼び出すとinjectが解決できずエラーになる。
2. 計画ページの大幅リデザイン - Miller Columns風レイアウト
従来のサイドパネル形式から、Miller Columns風の3カラムレイアウトに刷新した。
Before: サイドパネル形式
+------------------+--------+
| 計画リスト | 詳細 |
| (全ステータス) | パネル |
+------------------+--------+
After: Miller Columns風
+------------+------------+------------+
| ステータス | 計画リスト | 仕様書 |
| 選択 | (フィルタ) | プレビュー |
+------------+------------+------------+
ステータスの4分離:
| ステータス | 説明 | 表示位置 |
|---|---|---|
| pending | 未着手・検討中 | 左カラム上部 |
| in_progress | 実装中 | 左カラム中央 |
| completed | 完了 | 左カラム下部 |
| archived | アーカイブ済み | 左カラム最下部(折りたたみ) |
実装のポイント:
<template>
<div class="plans-page miller-columns">
<!-- 左カラム: ステータス選択 -->
<div class="status-column">
<StatusCard
v-for="status in statuses"
:key="status"
:status="status"
:count="getCountByStatus(status)"
:selected="selectedStatus === status"
@click="selectStatus(status)"
/>
</div>
<!-- 中央カラム: 計画リスト -->
<div class="plans-column">
<PlanCard
v-for="plan in filteredPlans"
:key="plan.path"
:plan="plan"
:selected="selectedPlan?.path === plan.path"
@click="selectPlan(plan)"
/>
</div>
<!-- 右カラム: 仕様書プレビュー -->
<div class="spec-column">
<SpecPreview
v-if="selectedPlan"
:plan="selectedPlan"
:spec="specContent"
/>
</div>
</div>
</template>
<script setup lang="ts">
const selectedStatus = ref<PlanStatus>('in_progress')
const selectedPlan = ref<Plan | null>(null)
const filteredPlans = computed(() =>
plans.value.filter(p => p.status === selectedStatus.value)
)
</script>
スタイリング:
.miller-columns {
display: grid;
grid-template-columns: 200px 1fr 1fr;
gap: 16px;
height: calc(100vh - 64px);
}
.status-column {
border-right: 1px solid var(--border-color);
overflow-y: auto;
}
.plans-column {
overflow-y: auto;
}
.spec-column {
overflow-y: auto;
background: var(--bg-secondary);
}
3. 仕様書表示機能(requirements.mdの表示)
計画ページの右カラムに仕様書(requirements.md)を表示する機能を追加した。
目次展開機能:
Markdownの見出しから自動で目次を生成し、クリックで該当箇所にスクロール。
const extractHeadings = (markdown: string): Heading[] => {
const lines = markdown.split('\n')
return lines
.filter(line => line.startsWith('#'))
.map(line => {
const match = line.match(/^(#{1,6})\s+(.+)$/)
if (!match) return null
return {
level: match[1].length,
text: match[2],
id: slugify(match[2])
}
})
.filter(Boolean) as Heading[]
}
画像パス変換:
相対パスで記述された画像を正しく表示するため、パス変換を実装。
const convertImagePaths = (markdown: string, basePath: string): string => {
//  → 
return markdown.replace(
/!\[([^\]]*)\]\(\.\/([^)]+)\)/g,
(_, alt, path) => ``
)
}
4. GMO CSV仕訳インポート計画
GMO決済の取引履歴CSVを仕訳に変換するスラッシュコマンドの設計を行った。
新規スラッシュコマンド: /import-gmo-csv
# コマンド仕様
名前: /import-gmo-csv
入力: GMO取引履歴CSVファイル
出力: 仕訳データ(SQLiteに保存)
CSVフォーマット(GMO取引履歴):
| カラム | 内容 | 例 |
|---|---|---|
| 取引日 | 決済日 | 2026/01/15 |
| 取引ID | GMOの取引識別子 | GMO-123456 |
| 取引種別 | 売上/返金/チャージバック | 売上 |
| 決済金額 | 顧客への請求額 | 10,000 |
| 手数料 | GMO手数料 | 350 |
| 入金額 | 実際の入金額 | 9,650 |
| 摘要 | 取引の説明 | ECサイト購入 |
仕訳パターン:
【売上時】
借方: 売掛金(GMO) 10,000円
貸方: 売上高 10,000円
【入金時(手数料相殺)】
借方: 普通預金 9,650円
借方: 支払手数料 350円
貸方: 売掛金(GMO) 10,000円
Codexレビュー結果:
設計書をCodex CLI(GPT-5.2)でレビューした結果、以下の指摘を受けて修正。
| 指摘 | 対応 |
|---|---|
| 返金時の仕訳パターンが未定義 | 返金パターンを追加 |
| 月跨ぎ決済の扱いが曖昧 | 決済日ベースで計上と明記 |
| 重複インポート防止策がない | 取引IDでのユニーク制約を追加 |
5. 日付形式統一マイグレーション完了
receiptsテーブルの日付形式をYYYY/MM/DDからYYYY-MM-DDに統一するマイグレーションを実行した。
マイグレーション内容:
-- 対象件数の確認
SELECT COUNT(*) FROM receipts WHERE date LIKE '%/%';
-- 結果: 70件
-- 日付形式の変換
UPDATE receipts
SET date = REPLACE(date, '/', '-')
WHERE date LIKE '%/%';
-- 変換結果の確認
SELECT date FROM receipts LIMIT 5;
-- 2026-01-15
-- 2026-01-14
-- 2026-01-13
-- ...
変換件数: 70件
全てのレコードがYYYY-MM-DD形式に統一された。
Pythonコード側の対応:
# 日付パースの統一関数
def parse_date(date_str: str) -> date:
"""複数フォーマットに対応した日付パース"""
for fmt in ['%Y-%m-%d', '%Y/%m/%d']:
try:
return datetime.strptime(date_str, fmt).date()
except ValueError:
continue
raise ValueError(f"Invalid date format: {date_str}")
# 日付フォーマットの統一関数
def format_date(d: date) -> str:
"""日付をYYYY-MM-DD形式に変換"""
return d.strftime('%Y-%m-%d')
6. 仕訳設計文書作成 - 総額パターン/純額パターン
GMO CSV仕訳インポートに関連して、仕訳の総額パターンと純額パターンを整理する設計文書を作成した。
総額パターンと純額パターンの違い:
| パターン | 借方 | 貸方 | 特徴 |
|---|---|---|---|
| 総額 | 普通預金 + 手数料 | 売掛金 | 取引の全体像が見える |
| 純額 | 普通預金 | 売上(純額) | シンプルだが手数料が埋もれる |
ベン図による関係整理:
┌─────────────────────────────────┐
│ 決済金額 │
│ ┌────────────┬──────────────┐ │
│ │ 入金額 │ 手数料 │ │
│ │ (9,650) │ (350) │ │
│ └────────────┴──────────────┘ │
│ (10,000) │
└─────────────────────────────────┘
採用方針:
- 総額パターンを採用: 手数料の可視化が重要
- 入金時に手数料を計上: 決済時ではなく入金時に相殺
7. 重複チェック機能強化計画
重複チェック画面に3択ラジオボタンを追加する計画を作成した。
現状の問題:
- 重複と判定されたレシートをどう処理するかの選択肢がない
- 「無効化」「帳票扱い」「保留」のいずれかを選びたい
3択ラジオボタン設計:
| 選択肢 | 動作 | ステータス変更 |
|---|---|---|
| 無効化 | このレシートを無効として扱う | status = 'disabled' |
| 帳票扱い | 重複ではなく別の帳票として扱う | status = 'active', duplicate_check = 'document' |
| 保留 | 判断を保留する | status = 'pending_review' |
フロー設計:
重複グループ表示
↓
グループ内のレシートを1つずつ確認
↓
各レシートに対して3択を選択
↓
「確定」ボタンで一括保存
↓
次の重複グループへ
UIモック:
<template>
<div class="duplicate-action">
<RadioGroup v-model="selectedAction">
<RadioButton value="disable">
<XCircle class="icon" />
無効化
</RadioButton>
<RadioButton value="document">
<FileText class="icon" />
帳票扱い
</RadioButton>
<RadioButton value="pending">
<Clock class="icon" />
保留
</RadioButton>
</RadioGroup>
</div>
</template>
8. デザインシステム更新 - 選択UIパターン
重複チェック機能の設計に合わせて、デザインシステムに選択UIパターンを追加した。
追加したパターン:
- ラジオボタングループ: 排他選択用
- チェックボックスグループ: 複数選択用
- トグルボタン: オン/オフ切替用
ラジオボタングループのスタイル:
.radio-group {
display: flex;
gap: 8px;
}
.radio-button {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 16px;
border: 1px solid var(--border-color);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.radio-button:hover {
border-color: var(--primary-color);
}
.radio-button.selected {
background: var(--primary-bg);
border-color: var(--primary-color);
color: var(--primary-color);
}
.radio-button .icon {
width: 16px;
height: 16px;
}
今日の学び
- Miller Columnsの有効性: macOSのFinderで使われているMiller Columns形式は、階層構造のナビゲーションに適している。ステータス→計画→仕様書という3階層を自然に表現できる
- inject問題の根本原因: VueのComposables APIはsetupコンテキスト内でのみ動作する。グローバルスコープでuseRouter等を呼び出すと「injection not found」エラーになる
- 総額vs純額の判断基準: 手数料を明示的に追跡したい場合は総額パターン、シンプルさを優先する場合は純額パターン。会計上はどちらも正しいが、管理目的で選択する
明日やること
- GMO CSV仕訳インポートの実装開始
- 重複チェック3択ラジオボタンの実装
- 計画ページの仕様書プレビューにMarkdown描画を追加
関連計画ファイル
| ファイル | ステータス | 内容 |
|---|---|---|
| gmo-csv-import-plan.md | planned | GMO CSV仕訳インポート |
| duplicate-action-plan.md | planned | 重複チェック3択ラジオボタン |
| plans-page-redesign.md | completed | 計画ページMiller Columns化 |
| date-format-unification.md | completed | 日付形式統一 |