feat: カルテのユースケース(作成・訂正・閲覧)を追加#84
Closed
KinjiKawaguchi wants to merge 209 commits into
Closed
Conversation
…bit, member, memberEvent, and memberExhibit
…dAccount, memberEvent, and base repository
… refactor repository methods
…hibit, and base services
…nd project structure
Chore/export-domain-type
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary - `domain/value-objects/` を廃止し、各値オブジェクトを使用元の集約ディレクトリに移動 - `Email`, `UniversityEmail`, `Departments` → `aggregates/member/` - `LightningTalkDuration`, `Url` → `aggregates/event/` - `ValueObject` 基底クラスを `domain/base/` に移動 - 型別ではなくドメイン概念別のディレクトリ構成に統一 ## Motivation 既存の構造では値オブジェクトがビルディングブロックの型別(`value-objects/`)にトップレベルで切り出されており、どの集約に属する概念なのかが不明瞭だった。 各値オブジェクトは実際には特定の集約でのみ使用されているため、使用元の集約ディレクトリに配置することで、ドメイン概念の凝集度を高める。 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
カルテ(相談記録)ドメインの新規集約と、作成・更新・閲覧・一覧取得のユースケースを追加するPRです。あわせて学籍番号や所属情報などの共有ドメイン型を導入しています。
Changes:
- カルテ集約(Karte/Client/Consent/Consultation/Response等)と
KarteRepository(フィルタ付きfindAll)を追加 - カルテの作成・更新(部分更新)・取得・一覧取得ユースケースを追加
- 共有ドメインとして所属(Affiliation)と学籍番号(StudentId)を追加、例外型を拡充
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/domain/index.ts | shared ドメイン公開を追加し、他層から参照可能に |
| src/domain/aggregates/index.ts | karte 集約のエクスポートを追加 |
| src/domain/aggregates/karte/index.ts | karte 配下の型/クラス公開を追加 |
| src/domain/aggregates/karte/Karte.ts | カルテ集約ルートと更新/スナップショット生成を追加 |
| src/domain/aggregates/karte/KarteRepository.ts | KarteFilter と Repository I/F を追加 |
| src/domain/aggregates/karte/Client.ts | 相談者(学生/教職員)型を追加 |
| src/domain/aggregates/karte/Consent.ts | 同意事項の値型を追加 |
| src/domain/aggregates/karte/Consultation.ts | 相談事の値型を追加 |
| src/domain/aggregates/karte/ConsultationCategory.ts | 相談カテゴリVO(バリデーション付き)を追加 |
| src/domain/aggregates/karte/Response.ts | 対応事の値型を追加 |
| src/domain/aggregates/karte/Resolution.ts | 解決ステータス型を追加 |
| src/domain/aggregates/karte/FollowUpDestination.ts | 後処理先型を追加 |
| src/domain/aggregates/karte/WorkDuration.ts | 作業時間(記録あり/なし)の表現を追加 |
| src/application/karte/index.ts | karte ユースケース公開を追加 |
| src/application/karte/CreateKarteUseCase.ts | カルテ作成ユースケースを追加 |
| src/application/karte/UpdateKarteUseCase.ts | カルテ更新(部分更新)ユースケースを追加 |
| src/application/karte/GetKarteUseCase.ts | カルテ取得ユースケースを追加 |
| src/application/karte/ListKartesUseCase.ts | カルテ一覧(フィルタ)ユースケースを追加 |
| src/domain/shared/index.ts | shared エクスポートの追加 |
| src/domain/shared/StudentId.ts | 学籍番号VO(正規化+バリデーション)を追加 |
| src/domain/shared/affiliation/index.ts | affiliation エクスポートの追加 |
| src/domain/shared/affiliation/Affiliation.ts | 所属VO群(学部/修士/博士/専門職)を追加 |
| src/domain/shared/affiliation/universityStructure.ts | 静岡大学の所属構造を型レベルで定義 |
| src/domain/exceptions/DomainExceptions.ts | 所属・学籍番号・カテゴリ・作業時間などの例外型を追加 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This was referenced Mar 12, 2026
Closed
## Summary - vitest v4をdevDependenciesに追加し、`npm test` / `npm run test:watch` スクリプトを追加 - `vitest.config.ts`でパスエイリアス(`#domain`, `#application`, `#infrastructure`)を解決 - `ValueObject`基底クラスのユニットテストをサンプルとして追加(8テストケース) - GitHub Actions `test.yml`ワークフローを追加(push/PR時に自動実行) ## Test plan - [x] `npm test` で8件のテストが全てパスすること - [x] `npx biome check` がパスすること - [x] GitHub Actions test workflowが正常に実行されること 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/su-its/core/pull/83" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary - 4つの個別CIワークフロー(build/lint/typecheck/test)を `ci.yml` に統合(Reusable Workflow) - `setup-node` Composite Action でセットアップ処理(setup-node + npm ci)を共通化 - `publish.yml` が `ci.yml` を前提条件として呼び出すオーケストレーションを追加 - 全ジョブに `permissions: contents: read` と `timeout-minutes: 10` を設定 - アクションを SHA pinning に更新(Node.js 20 deprecation 警告の解消) ## マージ後に必要な作業 - [ ] Branch Protection Rules の required status checks を `CI / lint`, `CI / typecheck`, `CI / test`, `CI / build` に更新 - [ ] NPM_TOKEN シークレットの更新(Publish失敗の根本原因) - [ ] GitHub Settings → Environments で `npm` 環境を作成 - [ ] 旧 `code_check.yml` ワークフローの無効化 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/su-its/core/pull/88" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary - 学籍番号(StudentId)の値オブジェクトをSharedKernelとして追加 - 旧形式(8桁数字)と新形式(3桁数字+英字+4桁数字)の両方に対応 - `fromString` ファクトリメソッドで小文字→大文字正規化・空白トリムを実施 - private constructorにより直接インスタンス化を防止 - 12件のユニットテストを追加 ## Test plan - [x] 旧形式・新形式の正常系テスト - [x] 大文字正規化・空白トリムのテスト - [x] 桁数不正・英字のみ・英字位置不正・空文字等の異常系テスト - [x] equalsメソッドの等価性テスト - [x] lint, typecheck, test全てパス ## Related - PR #81 から StudentId 部分を分割したPR - マージ後、PR #81 をリベースしてAffiliation部分のみにする 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/su-its/core/pull/87" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary - `Event.removeExhibitMemberId`のボディが空で、展示からのメンバー削除がno-opだった問題を修正 - 該当Exhibitからメンバーを削除し、他のExhibitにも所属していなければEvent.memberIdsからも削除する - PR #81 のレビューで指摘された pre-existing issue への対応 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/su-its/core/pull/85" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary - 値オブジェクトを所属する集約ディレクトリに移動し、`domain/base` に `ValueObject` を配置 - Shared Kernel として `Affiliation`(学部・修士・博士・専門職)と `StudentId` 値オブジェクトを追加 - 静岡大学の全組織構造を型レベルの判別共用体で表現(学部ごとに異なる階層構造を正確にモデル化) - Affiliation のバリデーションは型制約で保証し、ランタイム検証はシステム境界の責務とする設計 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/su-its/core/pull/81" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
98409a8 to
658bcfd
Compare
9b29cef to
7fe507e
Compare
## 概要
PC相談室のカルテ(相談記録)を表すKarte集約を追加する。
## 背景
PC相談室では相談対応の記録をカルテとして管理している。このPRではカルテのドメインモデルを定義し、ドメイン層に集約として追加する。
## 集約の構造
```
Karte(集約ルート / immutable)
├── Client 相談者(Student | Teacher | Staff | Other)
├── Consent 同意事項
├── Consultation 相談事
│ ├── categories 相談カテゴリ(複数選択)
│ ├── targetDevice 対象機器
│ └── troubleDetails トラブル詳細
└── SupportRecord 対応記録
├── assignedMemberIds 担当メンバー
├── content 対応内容
├── resolution 解決ステータス(resolved | unresolved → FollowUp)
└── workDuration 作業時間
```
## 設計の要点
### 1. immutableな「記録型」集約
カルテは一度書いたら変わらない「記録」としてimmutableに設計した。
- `create()` で新規作成、`correct()` で訂正(記録ミスの修正用)
- `correct()` は `recordedAt` を保持し `lastUpdatedAt` のみ更新する
- `reconstruct()` は永続化データからの復元用(バリデーションなし)
### 2. Recorded\<T\> — 過去データの欠損を型で表現
過去のカルテには一部フィールドが存在しない。null/undefinedに暗黙の意味を持たせず、`{ type: "recorded",
value: T } | { type: "notRecorded" }` の判別共用体で明示する。
- `create()` では全フィールドを生の値で受け取り、内部で `recorded()` に包む → 新規カルテは常に完全
- `reconstruct()` では `Recorded<T>` をそのまま受け取る → 過去データの欠損を許容
### 3. Branded型 + ファクトリ関数によるVO実装
`WorkDuration`, `KarteId` などの値オブジェクトは、既存の `ValueObject<T>`
基底クラスを使わずbranded型 + ファクトリ関数で実装した。
理由: `ValueObject<T>` 基底クラスは `equals()` が JSON.stringify
比較に依存しており実装として脆い。今後他の集約でも基底クラスから剥がしていく可能性があるため、新規コードでは採用しなかった。
### 4. KarteRepositoryは find/save のみ
ユースケースが明確なメソッドだけ定義し、CRUD網羅はしない(YAGNI)。検索や一覧のユースケースが出た時点で追加する。
### 5. Recorded\<T\> の値にはドメインルールを適用
`Recorded<T>` が `recorded` であれば、その値はドメインルールに従うべき。
- `assignedMemberIds`: `Recorded<NonEmptyArray<string>>` — 記録されているなら1人以上
- `categories`: `Recorded<NonEmptyArray<ConsultationCategory>>` —
記録されているなら1つ以上
## 議論したいこと
- **カルテは本当に変更すべきでないか?**
現在の設計は「記録型(immutable)」を前提としている。correct()は記録ミスの訂正用として用意したが、そもそもカルテは一度記録したら変更不可とすべきか、それとも訂正を許容すべきか。
- **変更履歴を残すべきか?**
現在のcorrect()は最新の状態だけを保持し、変更前の内容は残らない。訂正を許容する場合、誰がいつ何を変更したかの履歴(監査ログ)が必要ではないか。
- **FollowUp「その他」選択時の自由記述フィールド** が必要か(ドメインエキスパートとの議論待ち)
## テスト
- `WorkDuration`: バリデーション境界値(0分OK、負数/小数/NaN → 例外)
- `Karte`:
create/correctのドメインロジック(タイムスタンプ、Recorded包装、訂正時のid/recordedAt保持)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
7fe507e to
bc1576f
Compare
## Why 集約ルートのID(Member, Event)および子エンティティのID(Exhibit)が`string`型であり、異なるID種別の取り違えがコンパイル時に検出できなかった。既存のKarteIdパターンに合わせてBranded Typeを導入し、型安全性を確保する。 ## What - `MemberId`, `EventId`, `ExhibitId` のBranded Type定義とファクトリ関数を追加 - ドメイン層(集約ルート、エンティティ、リポジトリインターフェース、値オブジェクト)の全ID参照をBranded Typeに変更 - アプリケーション層(全ユースケースのInput型)をBranded Typeに変更 - インフラ層(DrizzleRepository)のDB↔ドメイン変換境界でファクトリ関数によるキャストを追加 - テストコードのassignedMemberIdsをMemberIdに更新 ## BREAKING CHANGE 全集約ルートID(MemberId, EventId)およびExhibitIdの型が`string`からBranded Typeに変更。 呼び出し側は`memberId()`, `eventId()`, `exhibitId()`ファクトリ関数でIDを生成する必要がある。 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/su-its/core/pull/92" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR #82のドメインモデルに合わせてユースケースを再設計した。 - UpdateKarte → CorrectKarte: イミュータブルなKarte.correct()に対応 - ListKartesを削除: RepositoryがfindById/saveのみのYAGNI設計 - Response → SupportRecord: 型名変更に追従 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
プレゼン層でのエラー種別分岐を可能にするため、汎用Errorから 既存パターンに合わせたApplicationExceptionに変更する。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bc1576f to
7443007
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
PR #82 で導入したカルテ集約のドメインモデルに対し、アプリケーション層のユースケースを追加する。
変更内容
新規ファイル
src/application/usecase/karte/CreateKarte.ts— カルテ新規作成src/application/usecase/karte/CorrectKarte.ts— カルテの訂正src/application/usecase/karte/GetKarte.ts— カルテ閲覧src/application/usecase/karte/index.ts— barrel export変更ファイル
src/application/index.ts— karteユースケースのexportを追加設計の要点
ユースケースの粒度
Karte.create()を呼び出し、全フィールドが揃った状態で新規作成するkarte.correct()で訂正した新しいインスタンスを保存する。「更新(Update)」ではなく「訂正(Correct)」という業務概念を採用旧PR #84からの変更点
UpdateKarteUseCase(部分更新)CorrectKarteUseCase(全フィールド訂正)correct()が全内容を受け取るListKartesUseCase+KarteFilterfindById/saveのみのYAGNI設計Response型SupportRecord型new Karte(...)Karte.create(...)一覧取得について
現時点ではRepositoryに
findAllを設けていない。一覧取得が必要になった段階で、クエリ専用のRead Modelやビュー層での対応を検討する(CQRSの方向性)。テスト計画
npx tsc --noEmitで型チェックパスnpx biome checkでリント・フォーマットパス🤖 Generated with Claude Code