From 6b2fa71600ec13345bf9c682e583ef5780d0d514 Mon Sep 17 00:00:00 2001 From: KinjiKawaguchi Date: Sun, 15 Mar 2026 09:20:32 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E3=82=AB=E3=83=AB=E3=83=86?= =?UTF-8?q?=E3=81=AE=E3=83=A6=E3=83=BC=E3=82=B9=E3=82=B1=E3=83=BC=E3=82=B9?= =?UTF-8?q?=EF=BC=88=E4=BD=9C=E6=88=90=E3=83=BB=E8=A8=82=E6=AD=A3=E3=83=BB?= =?UTF-8?q?=E9=96=B2=E8=A6=A7=EF=BC=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #82のドメインモデルに合わせてユースケースを再設計した。 - UpdateKarte → CorrectKarte: イミュータブルなKarte.correct()に対応 - ListKartesを削除: RepositoryがfindById/saveのみのYAGNI設計 - Response → SupportRecord: 型名変更に追従 Co-Authored-By: Claude Opus 4.6 --- src/application/index.ts | 1 + src/application/usecase/karte/CorrectKarte.ts | 57 +++++++++++++++++++ src/application/usecase/karte/CreateKarte.ts | 52 +++++++++++++++++ src/application/usecase/karte/GetKarte.ts | 26 +++++++++ src/application/usecase/karte/index.ts | 6 ++ 5 files changed, 142 insertions(+) create mode 100644 src/application/usecase/karte/CorrectKarte.ts create mode 100644 src/application/usecase/karte/CreateKarte.ts create mode 100644 src/application/usecase/karte/GetKarte.ts create mode 100644 src/application/usecase/karte/index.ts diff --git a/src/application/index.ts b/src/application/index.ts index 46ce8c6..6a2038e 100644 --- a/src/application/index.ts +++ b/src/application/index.ts @@ -1,4 +1,5 @@ export * from "./usecase/member"; export * from "./usecase/event"; export * from "./usecase/eventParticipation"; +export * from "./usecase/karte"; export * from "./exceptions"; diff --git a/src/application/usecase/karte/CorrectKarte.ts b/src/application/usecase/karte/CorrectKarte.ts new file mode 100644 index 0000000..b1ab73e --- /dev/null +++ b/src/application/usecase/karte/CorrectKarte.ts @@ -0,0 +1,57 @@ +import { IUseCase } from "#application/usecase/base"; +import type { Client } from "#domain/aggregates/karte/Client"; +import type { Consent } from "#domain/aggregates/karte/Consent"; +import type { ConsultationCategory } from "#domain/aggregates/karte/ConsultationCategory"; +import type { FollowUp } from "#domain/aggregates/karte/FollowUp"; +import type { Karte } from "#domain/aggregates/karte/Karte"; +import type { KarteId } from "#domain/aggregates/karte/KarteId"; +import type { KarteRepository } from "#domain/aggregates/karte/KarteRepository"; +import type { WorkDuration } from "#domain/aggregates/karte/WorkDuration"; +import type { NonEmptyArray } from "#domain/base/NonEmptyArray"; + +/** 訂正時の解決ステータス */ +type CorrectResolution = + | { readonly type: "resolved" } + | { readonly type: "unresolved"; readonly followUp: FollowUp }; + +export type CorrectKarteInput = { + readonly karteId: KarteId; + readonly consultedAt: Date; + readonly client: Client; + readonly consent: Consent; + readonly consultation: { + readonly categories: NonEmptyArray; + readonly targetDevice: string; + readonly troubleDetails: string; + }; + readonly supportRecord: { + readonly assignedMemberIds: NonEmptyArray; + readonly content: string; + readonly resolution: CorrectResolution; + readonly workDuration: WorkDuration; + }; +}; + +export type CorrectKarteOutput = { + readonly karte: Karte; +}; + +export class CorrectKarteUseCase extends IUseCase< + CorrectKarteInput, + CorrectKarteOutput +> { + constructor(private readonly karteRepository: KarteRepository) { + super(); + } + + async execute(input: CorrectKarteInput): Promise { + const existing = await this.karteRepository.findById(input.karteId); + if (!existing) { + throw new Error(`カルテが見つかりません: ${input.karteId}`); + } + + const corrected = existing.correct(input); + await this.karteRepository.save(corrected); + return { karte: corrected }; + } +} diff --git a/src/application/usecase/karte/CreateKarte.ts b/src/application/usecase/karte/CreateKarte.ts new file mode 100644 index 0000000..397f15f --- /dev/null +++ b/src/application/usecase/karte/CreateKarte.ts @@ -0,0 +1,52 @@ +import { IUseCase } from "#application/usecase/base"; +import type { Client } from "#domain/aggregates/karte/Client"; +import type { Consent } from "#domain/aggregates/karte/Consent"; +import type { ConsultationCategory } from "#domain/aggregates/karte/ConsultationCategory"; +import type { FollowUp } from "#domain/aggregates/karte/FollowUp"; +import { Karte } from "#domain/aggregates/karte/Karte"; +import type { KarteId } from "#domain/aggregates/karte/KarteId"; +import type { KarteRepository } from "#domain/aggregates/karte/KarteRepository"; +import type { WorkDuration } from "#domain/aggregates/karte/WorkDuration"; +import type { NonEmptyArray } from "#domain/base/NonEmptyArray"; + +/** 新規カルテ作成時の解決ステータス */ +type CreateResolution = + | { readonly type: "resolved" } + | { readonly type: "unresolved"; readonly followUp: FollowUp }; + +export type CreateKarteInput = { + readonly id: KarteId; + readonly consultedAt: Date; + readonly client: Client; + readonly consent: Consent; + readonly consultation: { + readonly categories: NonEmptyArray; + readonly targetDevice: string; + readonly troubleDetails: string; + }; + readonly supportRecord: { + readonly assignedMemberIds: NonEmptyArray; + readonly content: string; + readonly resolution: CreateResolution; + readonly workDuration: WorkDuration; + }; +}; + +export type CreateKarteOutput = { + readonly karte: Karte; +}; + +export class CreateKarteUseCase extends IUseCase< + CreateKarteInput, + CreateKarteOutput +> { + constructor(private readonly karteRepository: KarteRepository) { + super(); + } + + async execute(input: CreateKarteInput): Promise { + const karte = Karte.create(input); + await this.karteRepository.save(karte); + return { karte }; + } +} diff --git a/src/application/usecase/karte/GetKarte.ts b/src/application/usecase/karte/GetKarte.ts new file mode 100644 index 0000000..7827978 --- /dev/null +++ b/src/application/usecase/karte/GetKarte.ts @@ -0,0 +1,26 @@ +import { IUseCase } from "#application/usecase/base"; +import type { Karte } from "#domain/aggregates/karte/Karte"; +import type { KarteId } from "#domain/aggregates/karte/KarteId"; +import type { KarteRepository } from "#domain/aggregates/karte/KarteRepository"; + +export type GetKarteInput = { + readonly karteId: KarteId; +}; + +export type GetKarteOutput = { + readonly karte: Karte; +}; + +export class GetKarteUseCase extends IUseCase { + constructor(private readonly karteRepository: KarteRepository) { + super(); + } + + async execute(input: GetKarteInput): Promise { + const karte = await this.karteRepository.findById(input.karteId); + if (!karte) { + throw new Error(`カルテが見つかりません: ${input.karteId}`); + } + return { karte }; + } +} diff --git a/src/application/usecase/karte/index.ts b/src/application/usecase/karte/index.ts new file mode 100644 index 0000000..dc2af18 --- /dev/null +++ b/src/application/usecase/karte/index.ts @@ -0,0 +1,6 @@ +export { CreateKarteUseCase } from "./CreateKarte"; +export type { CreateKarteInput, CreateKarteOutput } from "./CreateKarte"; +export { CorrectKarteUseCase } from "./CorrectKarte"; +export type { CorrectKarteInput, CorrectKarteOutput } from "./CorrectKarte"; +export { GetKarteUseCase } from "./GetKarte"; +export type { GetKarteInput, GetKarteOutput } from "./GetKarte"; From ef44bf971d90b974d6716bd4e04f44e47b9af038 Mon Sep 17 00:00:00 2001 From: KinjiKawaguchi Date: Sat, 21 Mar 2026 00:52:03 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=E3=82=AB=E3=83=AB=E3=83=86?= =?UTF-8?q?=E6=9C=AA=E6=A4=9C=E5=87=BA=E6=99=82=E3=81=AB=E5=9E=8B=E4=BB=98?= =?UTF-8?q?=E3=81=8D=E3=82=A8=E3=83=A9=E3=83=BCKarteNotFoundException?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit プレゼン層でのエラー種別分岐を可能にするため、汎用Errorから 既存パターンに合わせたApplicationExceptionに変更する。 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/application/exceptions/ApplicationExceptions.ts | 8 ++++++++ src/application/usecase/karte/CorrectKarte.ts | 3 ++- src/application/usecase/karte/GetKarte.ts | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/application/exceptions/ApplicationExceptions.ts b/src/application/exceptions/ApplicationExceptions.ts index 86bf68b..e024e66 100644 --- a/src/application/exceptions/ApplicationExceptions.ts +++ b/src/application/exceptions/ApplicationExceptions.ts @@ -53,6 +53,14 @@ export class MemberNotFoundFromDiscordAccountIdException extends ApplicationExce } } +export class KarteNotFoundException extends ApplicationException { + constructor(karteId: string) { + const message = `カルテが見つかりません: ${karteId}`; + super(message); + this.name = "KarteNotFoundException"; + } +} + export class DiscordAccountNotConnectedException extends ApplicationException { constructor(userId: string, discordUserId: string) { const message = `ユーザー: ${userId} のDiscordアカウントは紐づいていません: ${discordUserId}`; diff --git a/src/application/usecase/karte/CorrectKarte.ts b/src/application/usecase/karte/CorrectKarte.ts index b1ab73e..55599bf 100644 --- a/src/application/usecase/karte/CorrectKarte.ts +++ b/src/application/usecase/karte/CorrectKarte.ts @@ -1,3 +1,4 @@ +import { KarteNotFoundException } from "#application/exceptions"; import { IUseCase } from "#application/usecase/base"; import type { Client } from "#domain/aggregates/karte/Client"; import type { Consent } from "#domain/aggregates/karte/Consent"; @@ -47,7 +48,7 @@ export class CorrectKarteUseCase extends IUseCase< async execute(input: CorrectKarteInput): Promise { const existing = await this.karteRepository.findById(input.karteId); if (!existing) { - throw new Error(`カルテが見つかりません: ${input.karteId}`); + throw new KarteNotFoundException(input.karteId); } const corrected = existing.correct(input); diff --git a/src/application/usecase/karte/GetKarte.ts b/src/application/usecase/karte/GetKarte.ts index 7827978..6c6a85c 100644 --- a/src/application/usecase/karte/GetKarte.ts +++ b/src/application/usecase/karte/GetKarte.ts @@ -1,3 +1,4 @@ +import { KarteNotFoundException } from "#application/exceptions"; import { IUseCase } from "#application/usecase/base"; import type { Karte } from "#domain/aggregates/karte/Karte"; import type { KarteId } from "#domain/aggregates/karte/KarteId"; @@ -19,7 +20,7 @@ export class GetKarteUseCase extends IUseCase { async execute(input: GetKarteInput): Promise { const karte = await this.karteRepository.findById(input.karteId); if (!karte) { - throw new Error(`カルテが見つかりません: ${input.karteId}`); + throw new KarteNotFoundException(input.karteId); } return { karte }; } From f66359b5c64983c688563a734b35051e4ab744c6 Mon Sep 17 00:00:00 2001 From: KinjiKawaguchi Date: Sat, 21 Mar 2026 19:33:01 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20assignedMemberIds=E3=82=92MemberId?= =?UTF-8?q?=E5=9E=8B=E3=81=AB=E5=A4=89=E6=9B=B4=EF=BC=88Branded=20Type?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- src/application/usecase/karte/CorrectKarte.ts | 3 ++- src/application/usecase/karte/CreateKarte.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/application/usecase/karte/CorrectKarte.ts b/src/application/usecase/karte/CorrectKarte.ts index 55599bf..385696a 100644 --- a/src/application/usecase/karte/CorrectKarte.ts +++ b/src/application/usecase/karte/CorrectKarte.ts @@ -8,6 +8,7 @@ import type { Karte } from "#domain/aggregates/karte/Karte"; import type { KarteId } from "#domain/aggregates/karte/KarteId"; import type { KarteRepository } from "#domain/aggregates/karte/KarteRepository"; import type { WorkDuration } from "#domain/aggregates/karte/WorkDuration"; +import type { MemberId } from "#domain/aggregates/member/MemberId"; import type { NonEmptyArray } from "#domain/base/NonEmptyArray"; /** 訂正時の解決ステータス */ @@ -26,7 +27,7 @@ export type CorrectKarteInput = { readonly troubleDetails: string; }; readonly supportRecord: { - readonly assignedMemberIds: NonEmptyArray; + readonly assignedMemberIds: NonEmptyArray; readonly content: string; readonly resolution: CorrectResolution; readonly workDuration: WorkDuration; diff --git a/src/application/usecase/karte/CreateKarte.ts b/src/application/usecase/karte/CreateKarte.ts index 397f15f..dfa20bd 100644 --- a/src/application/usecase/karte/CreateKarte.ts +++ b/src/application/usecase/karte/CreateKarte.ts @@ -7,6 +7,7 @@ import { Karte } from "#domain/aggregates/karte/Karte"; import type { KarteId } from "#domain/aggregates/karte/KarteId"; import type { KarteRepository } from "#domain/aggregates/karte/KarteRepository"; import type { WorkDuration } from "#domain/aggregates/karte/WorkDuration"; +import type { MemberId } from "#domain/aggregates/member/MemberId"; import type { NonEmptyArray } from "#domain/base/NonEmptyArray"; /** 新規カルテ作成時の解決ステータス */ @@ -25,7 +26,7 @@ export type CreateKarteInput = { readonly troubleDetails: string; }; readonly supportRecord: { - readonly assignedMemberIds: NonEmptyArray; + readonly assignedMemberIds: NonEmptyArray; readonly content: string; readonly resolution: CreateResolution; readonly workDuration: WorkDuration;