-
Notifications
You must be signed in to change notification settings - Fork 0
feat: PC相談室カルテ集約のドメインモデルを追加 #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
3a28dbd
feat: Shared Kernel に Affiliation 値オブジェクトと StudentId を追加
KinjiKawaguchi 03f2f0b
refactor: Affiliationのランタイムバリデーションを削除
KinjiKawaguchi 02b34db
docs: 型定義にJSDocコメントと公式ページURLを追加
KinjiKawaguchi 7f8a687
refactor: 未使用のInvalidAffiliationExceptionを削除し、組織構造型にJSDocを追加
KinjiKawaguchi 770a75d
refactor: ファイルレベルJSDocから教育学部固有のリンクを除去
KinjiKawaguchi afe72d7
style: DomainExceptions.tsの空行をdevelopと一致させる
KinjiKawaguchi 65d74c4
docs: Affiliation型にユビキタス言語「所属」との対応をJSDocで明示
KinjiKawaguchi a5a7f7f
feat: PC相談室カルテ集約のドメインモデルを追加
KinjiKawaguchi bd602d1
docs: カルテ集約の型・フィールドにユビキタス言語との対応をJSDocで明示
KinjiKawaguchi 85e2d2e
refactor: PRレビューに基づきカルテ集約のドメインモデルを改善
KinjiKawaguchi 5f996de
refactor: ConsultationCategoryをハードコード定数に変更
KinjiKawaguchi 61bae36
fix: ConsultationCategoryの定義を正式な一覧に更新
KinjiKawaguchi 4ae669f
refactor: Karte集約の公開API設計を深化
KinjiKawaguchi 1e2b267
refactor: MemberIdブランド型を削除しstringに統一
KinjiKawaguchi b0c77b2
refactor: FacultyClientをTeacherClientにリネーム
KinjiKawaguchi 7b022eb
refactor: KarteSnapshotを廃止
KinjiKawaguchi fb7d056
refactor: WorkDurationをbranded typeに変更しRecordedで包む
KinjiKawaguchi e5a2add
fix: レビュー指摘の3件を修正
KinjiKawaguchi c3586ce
refactor: レビュー指摘に基づくカルテ集約の設計改善とテスト追加
KinjiKawaguchi b7d4417
refactor: Consultation.categoriesにNonEmptyArrayを適用し設計判断を記録
KinjiKawaguchi 5e192b1
chore: docs/design-decisions.mdをトラッキングから除外
KinjiKawaguchi eaa8cfb
style: Biomeのimport順序を修正
KinjiKawaguchi d149ca3
style: Biomeのimport順序とフォーマットを修正
KinjiKawaguchi 660def4
fix: Recorded型のbarrelエクスポートを追加
KinjiKawaguchi a235b71
refactor: Dateフィールドをgetterによる防御的コピーに変更
KinjiKawaguchi 8965305
fix: Dateの防御的コピーに合わせてテストの参照比較を値比較に変更
KinjiKawaguchi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| export * from "./event"; | ||
| export * from "./karte"; | ||
| export * from "./member"; |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import type { Affiliation } from "#domain/shared"; | ||
| import type { StudentId } from "#domain/shared"; | ||
|
|
||
| /** 学生の相談者 */ | ||
| type StudentClient = { | ||
| readonly type: "student"; | ||
| /** 学籍番号 */ | ||
| readonly studentId: StudentId; | ||
| /** 氏名 */ | ||
| readonly name: string; | ||
| /** 所属 */ | ||
| readonly affiliation: Affiliation; | ||
| }; | ||
|
|
||
| /** 教員の相談者 */ | ||
| type TeacherClient = { | ||
| readonly type: "teacher"; | ||
| /** 氏名 */ | ||
| readonly name: string; | ||
| }; | ||
|
|
||
| /** 職員の相談者 */ | ||
| type StaffClient = { | ||
| readonly type: "staff"; | ||
| /** 氏名 */ | ||
| readonly name: string; | ||
| }; | ||
|
|
||
| /** その他の相談者(学外者など) */ | ||
| type OtherClient = { | ||
| readonly type: "other"; | ||
| /** 氏名 */ | ||
| readonly name: string; | ||
| }; | ||
|
|
||
| /** 相談者 — PC相談室に相談を持ち込んだ人 */ | ||
| export type Client = StudentClient | TeacherClient | StaffClient | OtherClient; | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| /** | ||
| * 同意事項 | ||
| * | ||
| * カルテ記録における免責同意と情報公開同意をまとめた値オブジェクト。 | ||
| * カルテは同意なしでも作成できるため、各フィールドはbooleanで表現する。 | ||
| */ | ||
| export type Consent = { | ||
| /** 免責同意 */ | ||
| readonly liabilityConsent: boolean; | ||
| /** 情報公開同意 */ | ||
| readonly disclosureConsent: boolean; | ||
| }; |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import type { NonEmptyArray } from "#domain/base/NonEmptyArray"; | ||
| import type { ConsultationCategory } from "./ConsultationCategory"; | ||
| import type { Recorded } from "./Recorded"; | ||
|
|
||
| /** | ||
| * 相談事 | ||
| * | ||
| * 相談者が持ち込んだトラブルの内容をまとめた値オブジェクト。 | ||
| * カテゴリ・対象機器は過去データで未記録の場合がある。 | ||
| */ | ||
| export type Consultation = { | ||
| /** 相談カテゴリ(複数選択可) */ | ||
| readonly categories: Recorded<NonEmptyArray<ConsultationCategory>>; | ||
| /** 対象機器 */ | ||
| readonly targetDevice: Recorded<string>; | ||
| /** トラブル詳細 */ | ||
| readonly troubleDetails: string; | ||
| }; |
|
KinjiKawaguchi marked this conversation as resolved.
|
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| /** | ||
| * 相談カテゴリ — PC相談室で扱うトラブルの分類タグ | ||
| * | ||
| * IDと表示名のペアで構成され、追加はできるが削除はできない(履歴整合性のため)。 | ||
| */ | ||
| export type ConsultationCategory = { | ||
| /** カテゴリID */ | ||
| readonly id: ConsultationCategoryId; | ||
| /** 表示名 */ | ||
| readonly displayName: string; | ||
| }; | ||
|
|
||
| /** 定義済みカテゴリID */ | ||
| export type ConsultationCategoryId = | ||
| | "wifi_eduroam" | ||
| | "wifi_success" | ||
| | "wifi_smartphone" | ||
| | "usage_mac" | ||
| | "usage_fs" | ||
| | "usage_vpn" | ||
| | "usage_mail" | ||
| | "usage_gakujo" | ||
| | "usage_onedrive" | ||
| | "usage_printer" | ||
| | "usage_vm" | ||
| | "usage_ms_software" | ||
| | "hardware_pc" | ||
| | "problem_credential" | ||
| | "problem_windows" | ||
| | "problem_linux" | ||
| | "programming" | ||
| | "rent" | ||
| | "other"; | ||
|
KinjiKawaguchi marked this conversation as resolved.
|
||
|
|
||
| /** 定義済みカテゴリ一覧 */ | ||
| export const CONSULTATION_CATEGORIES: readonly ConsultationCategory[] = [ | ||
| { id: "wifi_eduroam", displayName: "eduroamに対する接続方法の相談" }, | ||
| { id: "wifi_success", displayName: "SUCCESSに対する接続方法の相談" }, | ||
| { | ||
| id: "wifi_smartphone", | ||
| displayName: "スマホからのWiFi接続方法に関する相談", | ||
| }, | ||
| { id: "usage_mac", displayName: "MacOSの使い方に関する相談" }, | ||
| { id: "usage_fs", displayName: "FSの使い方に関する相談" }, | ||
| { id: "usage_vpn", displayName: "VPNの使い方に関する相談" }, | ||
| { id: "usage_mail", displayName: "メールの使い方に関する相談" }, | ||
| { id: "usage_gakujo", displayName: "学情の使い方に関する相談" }, | ||
| { id: "usage_onedrive", displayName: "OneDriveの使い方に関する相談" }, | ||
| { id: "usage_printer", displayName: "プリンタの使い方に関する相談" }, | ||
| { id: "usage_vm", displayName: "Virtual Machineに関する相談" }, | ||
| { | ||
| id: "usage_ms_software", | ||
| displayName: "Microsoftのソフトウェアに関する相談", | ||
| }, | ||
| { id: "hardware_pc", displayName: "PCのハードウェアに関する問題の相談" }, | ||
| { id: "problem_credential", displayName: "資格情報に関する相談" }, | ||
| { id: "problem_windows", displayName: "Windowsに関する問題" }, | ||
| { id: "problem_linux", displayName: "Linuxに関する問題" }, | ||
| { id: "programming", displayName: "プログラミングに関する相談" }, | ||
| { id: "rent", displayName: "貸し出しに関する相談" }, | ||
| { id: "other", displayName: "その他の相談" }, | ||
| ] as const; | ||
|
KinjiKawaguchi marked this conversation as resolved.
|
||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| /** 後処理 — 相談対応後のアクション */ | ||
| export type FollowUp = | ||
| | "技術部" | ||
| | "生協" | ||
| | "情報基盤センター" | ||
| | "見送り" | ||
| | "その他"; |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| import type { NonEmptyArray } from "#domain/base/NonEmptyArray"; | ||
| import type { Client } from "./Client"; | ||
| import type { Consent } from "./Consent"; | ||
| import type { Consultation } from "./Consultation"; | ||
| import type { ConsultationCategory } from "./ConsultationCategory"; | ||
| import type { FollowUp } from "./FollowUp"; | ||
| import type { KarteId } from "./KarteId"; | ||
| import { type Recorded, recorded } from "./Recorded"; | ||
| import type { Resolution } from "./Resolution"; | ||
| import type { SupportRecord } from "./SupportRecord"; | ||
| import type { WorkDuration } from "./WorkDuration"; | ||
|
|
||
| /** 新規作成時の解決ステータス — 後処理は必須 */ | ||
| type CompleteResolution = | ||
| | { readonly type: "resolved" } | ||
| | { | ||
| readonly type: "unresolved"; | ||
| readonly followUp: FollowUp; | ||
| }; | ||
|
|
||
| /** | ||
| * カルテの内容を表す入力型 | ||
| * | ||
| * create/correctの共通部分。全フィールドが完全に揃った状態を要求する。 | ||
| * Recorded型は使わず、生の値を受け取る。 | ||
| */ | ||
| type KarteContentProps = { | ||
| readonly consultedAt: Date; | ||
| readonly client: Client; | ||
| readonly consent: Consent; | ||
| readonly consultation: { | ||
| readonly categories: NonEmptyArray<ConsultationCategory>; | ||
| readonly targetDevice: string; | ||
| readonly troubleDetails: string; | ||
| }; | ||
| readonly supportRecord: { | ||
| readonly assignedMemberIds: NonEmptyArray<string>; | ||
| readonly content: string; | ||
| readonly resolution: CompleteResolution; | ||
| readonly workDuration: WorkDuration; | ||
| }; | ||
| }; | ||
|
|
||
| /** 新規カルテ作成時の入力型 */ | ||
| type KarteCreationProps = KarteContentProps & { readonly id: KarteId }; | ||
|
|
||
| /** 永続化データからの復元時の入力型 */ | ||
| type KarteReconstructProps = { | ||
| readonly id: KarteId; | ||
| readonly recordedAt: Date; | ||
| readonly consultedAt: Recorded<Date>; | ||
| readonly lastUpdatedAt: Date; | ||
| readonly client: Recorded<Client>; | ||
| readonly consent: Consent; | ||
| readonly consultation: Consultation; | ||
| readonly supportRecord: SupportRecord; | ||
| }; | ||
|
KinjiKawaguchi marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * カルテ集約ルート | ||
| * | ||
| * PC相談室での相談記録を表す。 | ||
| * 同意事項・相談事・対応記録という3つのドメイン概念で構成される。 | ||
| * 作成時に全データが揃うイミュータブルな記録であり、 | ||
| * 修正は correct() による明示的な訂正操作のみ許可する。 | ||
| * | ||
| * 過去データでは一部フィールドが未記録(Recorded型のnotRecorded)の場合がある。 | ||
| * 新規作成時は create() により全フィールドの完全性を保証する。 | ||
| */ | ||
| export class Karte { | ||
| /** | ||
| * Dateはミュータブルなため、privateで保持しgetterで防御的コピーを返す。 | ||
| * readonlyは再代入を防ぐが、Date.setTime()等の破壊的メソッドは防げない。 | ||
| */ | ||
| private readonly _recordedAt: Date; | ||
| private readonly _lastUpdatedAt: Date; | ||
|
|
||
| private constructor( | ||
| public readonly id: KarteId, | ||
| recordedAt: Date, | ||
| /** 相談日時 */ | ||
| public readonly consultedAt: Recorded<Date>, | ||
| lastUpdatedAt: Date, | ||
| /** 相談者 */ | ||
| public readonly client: Recorded<Client>, | ||
| /** 同意事項 */ | ||
| public readonly consent: Consent, | ||
| /** 相談事 */ | ||
| public readonly consultation: Consultation, | ||
| /** 対応記録 */ | ||
| public readonly supportRecord: SupportRecord, | ||
| ) { | ||
| this._recordedAt = recordedAt; | ||
| this._lastUpdatedAt = lastUpdatedAt; | ||
| } | ||
|
|
||
| /** 記録日時 */ | ||
| get recordedAt(): Date { | ||
| return new Date(this._recordedAt); | ||
| } | ||
|
|
||
| /** 最終更新日時 */ | ||
| get lastUpdatedAt(): Date { | ||
| return new Date(this._lastUpdatedAt); | ||
| } | ||
|
|
||
| /** 新規カルテの作成 — 全フィールド完全であることを型で保証する */ | ||
| static create(props: KarteCreationProps): Karte { | ||
| const now = Date.now(); | ||
| return new Karte( | ||
| props.id, | ||
| new Date(now), | ||
| recorded(props.consultedAt), | ||
| new Date(now), | ||
| recorded(props.client), | ||
| props.consent, | ||
| toConsultation(props), | ||
| toSupportRecord(props), | ||
| ); | ||
|
KinjiKawaguchi marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** 永続化データからの復元 — バリデーションなし */ | ||
| static reconstruct(props: KarteReconstructProps): Karte { | ||
| return new Karte( | ||
| props.id, | ||
| props.recordedAt, | ||
| props.consultedAt, | ||
| props.lastUpdatedAt, | ||
| props.client, | ||
| props.consent, | ||
| props.consultation, | ||
| props.supportRecord, | ||
| ); | ||
| } | ||
|
KinjiKawaguchi marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * カルテの訂正 | ||
| * | ||
| * 記録ミスの修正など、既存カルテの内容を訂正した新しいインスタンスを返す。 | ||
| * recordedAt(元の記録日時)は保持し、lastUpdatedAt を現在時刻に更新する。 | ||
| * 完全な生の値を受け取り、不変条件は型で保証する。 | ||
| */ | ||
| correct(props: KarteContentProps): Karte { | ||
| return new Karte( | ||
| this.id, | ||
| this.recordedAt, | ||
|
KinjiKawaguchi marked this conversation as resolved.
KinjiKawaguchi marked this conversation as resolved.
|
||
| recorded(props.consultedAt), | ||
| new Date(), | ||
| recorded(props.client), | ||
| props.consent, | ||
| toConsultation(props), | ||
| toSupportRecord(props), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** 生の入力から Consultation(Recorded付き)を構築する */ | ||
| function toConsultation(props: KarteContentProps): Consultation { | ||
| return { | ||
| categories: recorded(props.consultation.categories), | ||
| targetDevice: recorded(props.consultation.targetDevice), | ||
| troubleDetails: props.consultation.troubleDetails, | ||
| }; | ||
| } | ||
|
|
||
| /** 生の入力から SupportRecord(Recorded付き)を構築する */ | ||
| function toSupportRecord(props: KarteContentProps): SupportRecord { | ||
| return { | ||
| assignedMemberIds: recorded(props.supportRecord.assignedMemberIds), | ||
| content: props.supportRecord.content, | ||
| resolution: recorded(toRecordedResolution(props.supportRecord.resolution)), | ||
| workDuration: recorded(props.supportRecord.workDuration), | ||
| }; | ||
| } | ||
|
|
||
| function toRecordedResolution(complete: CompleteResolution): Resolution { | ||
| if (complete.type === "resolved") { | ||
| return { type: "resolved" }; | ||
| } | ||
| return { | ||
| type: "unresolved", | ||
| followUp: recorded(complete.followUp), | ||
| }; | ||
| } | ||
|
KinjiKawaguchi marked this conversation as resolved.
|
||
|
KinjiKawaguchi marked this conversation as resolved.
|
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| /** カルテID — カルテを一意に識別するブランド型 */ | ||
| export type KarteId = string & { readonly __brand: unique symbol }; | ||
|
|
||
| export function karteId(value: string): KarteId { | ||
| return value as KarteId; | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import type { Karte } from "./Karte"; | ||
| import type { KarteId } from "./KarteId"; | ||
|
|
||
| export interface KarteRepository { | ||
| findById(id: KarteId): Promise<Karte | null>; | ||
| save(karte: Karte): Promise<void>; | ||
| } |
|
KinjiKawaguchi marked this conversation as resolved.
|
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /** | ||
| * 記録有無を明示する型 | ||
| * | ||
| * FDM原則に従い、null/undefinedに意味を持たせない。 | ||
| * 過去データで記録されていない可能性があるフィールドに使用する。 | ||
| */ | ||
| export type Recorded<T> = | ||
| | { | ||
| readonly type: "recorded"; | ||
| readonly value: T; | ||
| } | ||
| | { readonly type: "notRecorded" }; | ||
|
|
||
| /** 記録済みの値を生成する */ | ||
| export function recorded<T>(value: T): Recorded<T> { | ||
| return { type: "recorded", value }; | ||
| } | ||
|
|
||
| /** 未記録を生成する */ | ||
| export function notRecorded<T>(): Recorded<T> { | ||
| return { type: "notRecorded" }; | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import type { FollowUp } from "./FollowUp"; | ||
| import type { Recorded } from "./Recorded"; | ||
|
|
||
| /** | ||
| * 解決ステータス — 相談が解決したかどうか | ||
| * | ||
| * 未解決の場合、後処理は過去データでは未記録の場合がある。 | ||
| * FDM原則に従い、Recorded型で明示する。 | ||
| */ | ||
| export type Resolution = | ||
| | { readonly type: "resolved" } | ||
| | { | ||
| readonly type: "unresolved"; | ||
| /** 後処理 */ | ||
| readonly followUp: Recorded<FollowUp>; | ||
| }; |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.