メインコンテンツまでスキップ
バージョン: 最新 (develop)

申し送りに Normal/Abnormal フィールドを追加する手順書

概要

この手順書は、既存の申し送り機能(Shift Report)に Normal/Abnormal 値のペアフィールドを追加する方法を説明します。この実装パターンは、Rehabilitation(リハビリ)申し送りと Grooming(整容)申し送りで使用されています。

前提知識

Normal/Abnormal 値とは

  • Normal 値: 利用者の平常時の状態(マスターデータから自動取得)
  • Abnormal 値: 申し送り時点での異常時の状態(フロントエンドから入力)

データフロー

  1. フロントエンドは Abnormal 値のみ を送信
  2. バックエンド BFF 層で Normal 値を自動取得 してデータを補完
  3. データベースには 両方の値 を保存
  4. レスポンスには 両方の値 を返却

データソース

Normal 値のデータソースは docs/backend/normal_abnormal_values.md を参照してください。


バックエンド修正手順

1. Prisma スキーマの更新

ファイル: packages/backend/schema.prisma

既存のフィールドを Normal/Abnormal のペアに変更します。

変更前:

model ShiftReportGrooming {
clothingSelectionId Int?
upperClothingActionId Int?
// ...
}

変更後:

model ShiftReportGrooming {
clothingSelectionNormalId Int? // 服の選択(平常時)
clothingSelectionAbnormalId Int? // 服の選択(異常時)
upperClothingActionNormalId Int? // 上衣更衣動作(平常時)
upperClothingActionAbnormalId Int? // 上衣更衣動作(異常時)
// ...
}

2. マイグレーションファイルの作成と編集

ステップ 2.1: マイグレーションファイルを生成

cd packages/backend
npx prisma migrate dev --name add_normal_abnormal_to_[feature] --create-only

ステップ 2.2: 生成されたマイグレーションファイルを編集

既存データを保持するために、3ステップの移行処理を実装します。

ファイル: packages/backend/migrations/[timestamp]_add_normal_abnormal_to_[feature]/migration.sql

-- Step 1: 新しいカラムを追加
ALTER TABLE "ShiftReport[Feature]" ADD COLUMN "clothingSelectionNormalId" INTEGER;
ALTER TABLE "ShiftReport[Feature]" ADD COLUMN "clothingSelectionAbnormalId" INTEGER;
-- ... 他のフィールドも同様に追加

-- Step 2: 既存データを Abnormal カラムにコピー(既存データは異常値として扱う)
UPDATE "ShiftReport[Feature]"
SET
"clothingSelectionAbnormalId" = "clothingSelectionId",
"upperClothingActionAbnormalId" = "upperClothingActionId"
-- ... 他のフィールドも同様
WHERE "clothingSelectionId" IS NOT NULL
OR "upperClothingActionId" IS NOT NULL;
-- ... 他のフィールドも同様

-- Step 3: 古いカラムを削除
ALTER TABLE "ShiftReport[Feature]" DROP COLUMN "clothingSelectionId";
ALTER TABLE "ShiftReport[Feature]" DROP COLUMN "upperClothingActionId";
-- ... 他のフィールドも同様

3. エンティティの更新

ファイル: packages/backend/src/modules/shift-report-[feature]/shift-report-[feature].entity.ts

Normal/Abnormal フィールドのペアを追加します。

export type ShiftReport[Feature]Entity = {
// ... 既存フィールド
clothingSelectionNormalId: number | null;
clothingSelectionAbnormalId: number | null;
upperClothingActionNormalId: number | null;
upperClothingActionAbnormalId: number | null;
// ... 他のフィールド
};

4. リポジトリの更新

ファイル: packages/backend/src/modules/shift-report-[feature]/shift-report-[feature].repository.ts

ステップ 4.1: Input 型を更新

type SaveShiftReport[Feature]Input = {
// ... 既存フィールド
clothing?: {
clothingSelectionNormalId?: number | null;
clothingSelectionAbnormalId?: number | null;
upperClothingActionNormalId?: number | null;
upperClothingActionAbnormalId?: number | null;
// ... 他のフィールド
};
bathing?: {
bodyWashingActionNormalId?: number | null;
bodyWashingActionAbnormalId?: number | null;
};
basicAction?: {
maintainingSittingPositionNormalId?: number | null;
maintainingSittingPositionAbnormalId?: number | null;
standingActionNormalId?: number | null;
standingActionAbnormalId?: number | null;
maintainingStandingPositionNormalId?: number | null;
maintainingStandingPositionAbnormalId?: number | null;
};
};

ステップ 4.2: Prisma 入力の作成(スプレッド構文を使用)

const clothing = {
clothingSelectionNormalId: [feature].clothing?.clothingSelectionNormalId,
clothingSelectionAbnormalId: [feature].clothing?.clothingSelectionAbnormalId,
upperClothingActionNormalId: [feature].clothing?.upperClothingActionNormalId,
upperClothingActionAbnormalId: [feature].clothing?.upperClothingActionAbnormalId,
// ... 他のフィールド
};

const bathing = {
bodyWashingActionNormalId: [feature].bathing?.bodyWashingActionNormalId,
bodyWashingActionAbnormalId: [feature].bathing?.bodyWashingActionAbnormalId,
};

const basicAction = {
maintainingSittingPositionNormalId: [feature].basicAction?.maintainingSittingPositionNormalId,
maintainingSittingPositionAbnormalId: [feature].basicAction?.maintainingSittingPositionAbnormalId,
standingActionNormalId: [feature].basicAction?.standingActionNormalId,
standingActionAbnormalId: [feature].basicAction?.standingActionAbnormalId,
maintainingStandingPositionNormalId: [feature].basicAction?.maintainingStandingPositionNormalId,
maintainingStandingPositionAbnormalId: [feature].basicAction?.maintainingStandingPositionAbnormalId,
};

// upsert の createInput/updateInput で使用
createInput: {
...createSendTo,
...clothing, // スプレッド構文で展開
...bathing,
...basicAction,
// ... 他のフィールド
}

ステップ 4.3: Return 文を更新

return {
// ... 既存フィールド
clothingSelectionNormalId: result.clothingSelectionNormalId,
clothingSelectionAbnormalId: result.clothingSelectionAbnormalId,
upperClothingActionNormalId: result.upperClothingActionNormalId,
upperClothingActionAbnormalId: result.upperClothingActionAbnormalId,
// ... 他のフィールド
};

5. サービス層の更新

ファイル: packages/backend/src/modules/shift-report-[feature]/shift-report-[feature].service.ts

Input 型をリポジトリと同じように更新します。

type SaveShiftReport[Feature]Input = {
// リポジトリの Input 型と同じ構造
};

6. BFF 型定義の更新

ファイル: packages/backend/src/bff/native/shift-report-page/shift-report-[feature]-page/shift-report-[feature]-page.type.ts

ステップ 6.1: InputType の更新(Abnormal のみ)

@InputType()
export class Clothing[Feature]Input {
@Field(() => Int, { nullable: true })
clothingSelectionAbnormalId?: number | null;

@Field(() => Int, { nullable: true })
upperClothingActionAbnormalId?: number | null;

// ... 他の Abnormal フィールドのみ
}

@InputType()
export class Bathing[Feature]Input {
@Field(() => Int, { nullable: true })
bodyWashingActionAbnormalId?: number | null;
}

@InputType()
export class BasicAction[Feature]Input {
@Field(() => Int, { nullable: true })
maintainingSittingPositionAbnormalId?: number | null;

@Field(() => Int, { nullable: true })
standingActionAbnormalId?: number | null;

@Field(() => Int, { nullable: true })
maintainingStandingPositionAbnormalId?: number | null;
}

ステップ 6.2: ObjectType の更新(Normal と Abnormal の両方)

@ObjectType()
export class Clothing[Feature] {
@Field(() => OptionList, { nullable: true })
clothingSelectionNormal?: OptionList | null;

@Field(() => OptionList, { nullable: true })
clothingSelectionAbnormal?: OptionList | null;

@Field(() => OptionList, { nullable: true })
upperClothingActionNormal?: OptionList | null;

@Field(() => OptionList, { nullable: true })
upperClothingActionAbnormal?: OptionList | null;

// ... 他のフィールドも Normal/Abnormal ペア
}

@ObjectType()
export class Bathing[Feature] {
@Field(() => OptionList, { nullable: true })
bodyWashingActionNormal?: OptionList | null;

@Field(() => OptionList, { nullable: true })
bodyWashingActionAbnormal?: OptionList | null;
}

@ObjectType()
export class BasicAction[Feature] {
@Field(() => OptionList, { nullable: true })
maintainingSittingPositionNormal?: OptionList | null;

@Field(() => OptionList, { nullable: true })
maintainingSittingPositionAbnormal?: OptionList | null;

@Field(() => OptionList, { nullable: true })
standingActionNormal?: OptionList | null;

@Field(() => OptionList, { nullable: true })
standingActionAbnormal?: OptionList | null;

@Field(() => OptionList, { nullable: true })
maintainingStandingPositionNormal?: OptionList | null;

@Field(() => OptionList, { nullable: true })
maintainingStandingPositionAbnormal?: OptionList | null;
}

7. BFF サービス層の更新(重要)

ファイル: packages/backend/src/bff/native/shift-report-page/shift-report-[feature]-page/shift-report-[feature]-page.service.ts

ステップ 7.1: コンストラクタに必要なサービスを注入

constructor(
private readonly [feature]Service: ShiftReport[Feature]Service,
private readonly userCarePositionChangeService: UserCarePositionChangeService,
private readonly userRegularMobilityAssesmentService: UserRegularMobilityAssesmentService,
private readonly userRegularBathingAssesmentService: UserRegularBathingAssesmentService,
// ... 他のサービス
) {}

ステップ 7.2: saveShiftReport[Feature] メソッドで Normal 値を自動取得

async saveShiftReport[Feature](
input: SaveShiftReport[Feature]Input,
context: RequestContext,
): Promise<ServiceResult<ShiftReport[Feature]Response>> {
// 1. Normal 値のデータソースを取得
const carePositionChangeResult =
await this.userCarePositionChangeService.getCarePositionChange(
{ userId: input.userId },
context,
);

const mobilityAssessmentResult =
await this.userRegularMobilityAssesmentService.getUserRegularAssesmentMobility(
{ userId: input.userId },
context,
);

const bathingAssessmentResult =
await this.userRegularBathingAssesmentService.getUserRegularAssesmentBathing(
{ userId: input.userId },
context,
);

// 2. Normal 値を含む拡張入力を作成
const enhancedInput = {
...input,
clothing: {
...input.clothing,
// Normal 値を自動設定
clothingSelectionNormalId: carePositionChangeResult.success
? carePositionChangeResult.data?.clothingSelectionId
: null,
upperClothingActionNormalId: carePositionChangeResult.success
? carePositionChangeResult.data?.upperClothingActionId
: null,
lowerClothingActionNormalId: carePositionChangeResult.success
? carePositionChangeResult.data?.lowerClothingActionId
: null,
socksShoesActionNormalId: carePositionChangeResult.success
? carePositionChangeResult.data?.socksShoesActionId
: null,
oralCareActionNormalId: carePositionChangeResult.success
? carePositionChangeResult.data?.oralCareActionId
: null,
faceWashingActionNormalId: carePositionChangeResult.success
? carePositionChangeResult.data?.faceWashingActionId
: null,
hairdressingActionNormalId: carePositionChangeResult.success
? carePositionChangeResult.data?.hairdressingActionId
: null,
// Abnormal 値はフロントエンドから受け取る
clothingSelectionAbnormalId: input.clothing?.clothingSelectionAbnormalId,
upperClothingActionAbnormalId: input.clothing?.upperClothingActionAbnormalId,
lowerClothingActionAbnormalId: input.clothing?.lowerClothingActionAbnormalId,
socksShoesActionAbnormalId: input.clothing?.socksShoesActionAbnormalId,
oralCareActionAbnormalId: input.clothing?.oralCareActionAbnormalId,
faceWashingActionAbnormalId: input.clothing?.faceWashingActionAbnormalId,
hairdressingActionAbnormalId: input.clothing?.hairdressingActionAbnormalId,
},
bathing: {
...input.bathing,
bodyWashingActionNormalId: bathingAssessmentResult.success
? bathingAssessmentResult.data?.bodyWashingActionId
: null,
bodyWashingActionAbnormalId: input.bathing?.bodyWashingActionAbnormalId,
},
basicAction: {
...input.basicAction,
maintainingSittingPositionNormalId: carePositionChangeResult.success
? carePositionChangeResult.data?.maintainingSittingPositionId
: null,
standingActionNormalId: carePositionChangeResult.success
? carePositionChangeResult.data?.standingActionId
: null,
maintainingStandingPositionNormalId: mobilityAssessmentResult.success
? mobilityAssessmentResult.data?.maintainingStandingPositionId
: null,
maintainingSittingPositionAbnormalId: input.basicAction?.maintainingSittingPositionAbnormalId,
standingActionAbnormalId: input.basicAction?.standingActionAbnormalId,
maintainingStandingPositionAbnormalId: input.basicAction?.maintainingStandingPositionAbnormalId,
},
};

// 3. 拡張入力をモジュールサービスに渡す
const result = await this.[feature]Service.saveShiftReport[Feature](
enhancedInput,
context,
);

if (!result.success) {
return result;
}

// 4. レスポンス変換(後述)
return {
success: true,
data: await this.toShiftReport[Feature]Response(result.data, context),
};
}

ステップ 7.3: レスポンス変換メソッドを更新

4つのメソッドすべてで Normal/Abnormal フィールドのペアをマッピングします。

private async toShiftReport[Feature]Response(
entity: ShiftReport[Feature]Entity,
context: RequestContext,
): Promise<ShiftReport[Feature]Response> {
// ... 既存のマッピング

const clothing: Clothing[Feature] = {
clothingSelectionNormal: await this.matchOptions(
entity.clothingSelectionNormalId,
'listClothingSelectionResolver',
context,
),
clothingSelectionAbnormal: await this.matchOptions(
entity.clothingSelectionAbnormalId,
'listClothingSelectionResolver',
context,
),
upperClothingActionNormal: await this.matchOptions(
entity.upperClothingActionNormalId,
'listUpperClothingActionResolver',
context,
),
upperClothingActionAbnormal: await this.matchOptions(
entity.upperClothingActionAbnormalId,
'listUpperClothingActionResolver',
context,
),
// ... 他のフィールドも同様
};

const bathing: Bathing[Feature] = {
bodyWashingActionNormal: await this.matchOptions(
entity.bodyWashingActionNormalId,
'listBodyWashingActionResolver',
context,
),
bodyWashingActionAbnormal: await this.matchOptions(
entity.bodyWashingActionAbnormalId,
'listBodyWashingActionResolver',
context,
),
};

const basicAction: BasicAction[Feature] = {
maintainingSittingPositionNormal: await this.matchOptions(
entity.maintainingSittingPositionNormalId,
'listMaintainingSittingPositionResolver',
context,
),
maintainingSittingPositionAbnormal: await this.matchOptions(
entity.maintainingSittingPositionAbnormalId,
'listMaintainingSittingPositionResolver',
context,
),
standingActionNormal: await this.matchOptions(
entity.standingActionNormalId,
'listStandingActionResolver',
context,
),
standingActionAbnormal: await this.matchOptions(
entity.standingActionAbnormalId,
'listStandingActionResolver',
context,
),
maintainingStandingPositionNormal: await this.matchOptions(
entity.maintainingStandingPositionNormalId,
'listMaintainingStandingPositionResolver',
context,
),
maintainingStandingPositionAbnormal: await this.matchOptions(
entity.maintainingStandingPositionAbnormalId,
'listMaintainingStandingPositionResolver',
context,
),
};

return {
// ... 既存フィールド
clothing,
bathing,
basicAction,
// ... 他のフィールド
};
}

同様の変更を以下のメソッドにも適用:

  • toShiftReport[Feature]ListResponse
  • toDeletedShiftReport[Feature]Response
  • toShiftReport[Feature]DefaultResponse(こちらは Normal 値のみ返却、Abnormal は null)

8. getDefaultShiftReport[Feature] メソッドの修正(重要)

ファイル: packages/backend/src/bff/native/shift-report-page/shift-report-[feature]-page/shift-report-[feature]-page.service.ts

このメソッドはデフォルトの申し送りを作成する際に使用されます。Normal 値のみを返し、Abnormal 値は全て null にする必要があります

変更前(古いフィールド名):

async getDefaultShiftReport[Feature](
input: GetDefaultShiftReport[Feature]Input,
context: RequestContext,
): Promise<ServiceResult<ShiftReport[Feature]DefaultResponse>> {
// ... データソース取得処理

const matchedOptions = matchOptions({
// ケア体位変換から
maintainingSittingPositionId:
carePositionChangeResult.data?.maintainingSittingPositionId,
standingActionId: carePositionChangeResult.data?.standingActionId,
clothingSelectionId: carePositionChangeResult.data?.clothingSelectionId,
upperClothingActionId:
carePositionChangeResult.data?.upperClothingActionId,
lowerClothingActionId:
carePositionChangeResult.data?.lowerClothingActionId,
socksShoesActionId: carePositionChangeResult.data?.socksShoesActionId,
oralCareActionId: carePositionChangeResult.data?.oralCareActionId,
faceWashingActionId:
carePositionChangeResult.data?.faceBodyWashingActionId,
hairdressingActionId: carePositionChangeResult.data?.hairdressingActionId,
// 定期アセスメント(運動機能)から
maintainingStandingPositionId:
mobilityAssessmentResult.data?.maintainingStandingPositionId,
// 定期アセスメント(入浴)から
bodyWashingActionId: bathingAssessmentResult.data?.bodyWashingActionId,
});

// ... レスポンス構築
}

変更後(Normal値のフィールド名に変更、Abnormal値は全てnull):

async getDefaultShiftReport[Feature](
input: GetDefaultShiftReport[Feature]Input,
context: RequestContext,
): Promise<ServiceResult<ShiftReport[Feature]DefaultResponse>> {
// ... データソース取得処理

const matchedOptions = matchOptions({
// ケア体位変換から(Normal値として設定)
clothingSelectionNormalId: carePositionChangeResult.data?.clothingSelectionId,
upperClothingActionNormalId: carePositionChangeResult.data?.upperClothingActionId,
lowerClothingActionNormalId: carePositionChangeResult.data?.lowerClothingActionId,
socksShoesActionNormalId: carePositionChangeResult.data?.socksShoesActionId,
oralCareActionNormalId: carePositionChangeResult.data?.oralCareActionId,
faceWashingActionNormalId: carePositionChangeResult.data?.faceBodyWashingActionId,
hairdressingActionNormalId: carePositionChangeResult.data?.hairdressingActionId,
maintainingSittingPositionNormalId: carePositionChangeResult.data?.maintainingSittingPositionId,
standingActionNormalId: carePositionChangeResult.data?.standingActionId,
// 定期アセスメント(運動機能)から(Normal値として設定)
maintainingStandingPositionNormalId: mobilityAssessmentResult.data?.maintainingStandingPositionId,
// 定期アセスメント(入浴)から(Normal値として設定)
bodyWashingActionNormalId: bathingAssessmentResult.data?.bodyWashingActionId,
// Abnormal値は全てnull(デフォルト作成時は異常値が未入力)
clothingSelectionAbnormalId: null,
upperClothingActionAbnormalId: null,
lowerClothingActionAbnormalId: null,
socksShoesActionAbnormalId: null,
oralCareActionAbnormalId: null,
faceWashingActionAbnormalId: null,
hairdressingActionAbnormalId: null,
maintainingSittingPositionAbnormalId: null,
standingActionAbnormalId: null,
maintainingStandingPositionAbnormalId: null,
bodyWashingActionAbnormalId: null,
});

// ... レスポンス構築
}

重要なポイント:

  • マスターデータから取得した値は Normal フィールド に設定
  • Abnormal フィールドは全て null に設定(デフォルト作成時は異常値が未入力のため)
  • フィールド名は必ず NormalId または AbnormalId サフィックスを使用

9. ユーティリティ関数の更新(必要な場合)

ファイル: packages/backend/src/bff/native/shift-report-page/libs/util.ts

matchOptions 関数のパラメータに新しいフィールドを追加します。

export const matchOptions = async (
// ... 既存パラメータ
clothingSelectionNormalId?: number | null,
clothingSelectionAbnormalId?: number | null,
upperClothingActionNormalId?: number | null,
upperClothingActionAbnormalId?: number | null,
// ... 他の Normal/Abnormal フィールド
) => {
// ... 関数の実装
};

10. BFF モジュールの更新

ファイル: packages/backend/src/bff/native/shift-report-page/shift-report-[feature]-page/shift-report-[feature]-page.module.ts

必要なサービスを providers に追加します。

@Module({
imports: [
ShiftReport[Feature]Module,
UserCarePositionChangeModule,
UserRegularMobilityAssesmentModule,
UserRegularBathingAssesmentModule,
// ... 他のモジュール
],
providers: [
ShiftReport[Feature]PageResolver,
ShiftReport[Feature]PageService,
],
})
export class ShiftReport[Feature]PageModule {}

11. GraphQL スキーマドキュメント生成とマイグレーション実行

cd packages/backend

# マイグレーション実行
npx prisma migrate dev

# GraphQL スキーマドキュメント生成
npm run build:docs

フロントエンド修正手順

1. GraphQL クエリ/ミューテーションの更新

以下の4つのファイルを更新します:

  1. apps/blackswan-web/src/graphql/shift-report/[feature]/queries/getShiftReport[Feature].graphql
  2. apps/blackswan-web/src/graphql/shift-report/[feature]/queries/get-default-shift-report-[feature].graphql
  3. apps/blackswan-web/src/graphql/shift-report/[feature]/mutations/saveShiftReport[Feature]Mutation.graphql
  4. apps/blackswan-web/src/graphql/shift-report/[feature]/mutations/deleteShiftReport[Feature]Mutation.graphql

変更前:

clothing {
clothingSelection { id value }
upperClothingAction { id value }
lowerClothingAction { id value }
socksShoesAction { id value }
oralCareAction { id value }
faceWashingAction { id value }
hairdressingAction { id value }
}
bathing {
bodyWashingAction { id value }
}
basicAction {
maintainingSittingPosition { id value }
standingAction { id value }
maintainingStandingPosition { id value }
}

変更後:

clothing {
clothingSelectionNormal { id value }
clothingSelectionAbnormal { id value }
upperClothingActionNormal { id value }
upperClothingActionAbnormal { id value }
lowerClothingActionNormal { id value }
lowerClothingActionAbnormal { id value }
socksShoesActionNormal { id value }
socksShoesActionAbnormal { id value }
oralCareActionNormal { id value }
oralCareActionAbnormal { id value }
faceWashingActionNormal { id value }
faceWashingActionAbnormal { id value }
hairdressingActionNormal { id value }
hairdressingActionAbnormal { id value }
}
bathing {
bodyWashingActionNormal { id value }
bodyWashingActionAbnormal { id value }
}
basicAction {
maintainingSittingPositionNormal { id value }
maintainingSittingPositionAbnormal { id value }
standingActionNormal { id value }
standingActionAbnormal { id value }
maintainingStandingPositionNormal { id value }
maintainingStandingPositionAbnormal { id value }
}

2. バリデーションスキーマの更新

ファイル: apps/blackswan-web/src/sections/info-sharing/shift-report/schema/shift-report-schema.ts

変更前:

export const [feature]Schema = z.object({
...basicSchema.shape,
grooming: z.object({
facialExpressionId: z.number().optional().nullable(),
isNailClipping: z.boolean().optional().nullable(),
}),
clothing: z.object({
clothingSelectionId: z.number().optional().nullable(),
upperClothingActionId: z.number().optional().nullable(),
lowerClothingActionId: z.number().optional().nullable(),
socksShoesActionId: z.number().optional().nullable(),
oralCareActionId: z.number().optional().nullable(),
faceWashingActionId: z.number().optional().nullable(),
hairdressingActionId: z.number().optional().nullable(),
}),
bathing: z.object({
bodyWashingActionId: z.number().optional().nullable(),
}),
basicAction: z.object({
maintainingSittingPositionId: z.number().optional().nullable(),
standingActionId: z.number().optional().nullable(),
maintainingStandingPositionId: z.number().optional().nullable(),
}),
content: z.string().optional().nullable(),
images: z.array(z.any()).optional(),
deleteImageIds: z.array(z.string()).optional(),
});

変更後:

export const [feature]Schema = z.object({
...basicSchema.shape,
grooming: z.object({
facialExpressionId: z.number().optional().nullable(),
isNailClipping: z.boolean().optional().nullable(),
}),
clothing: z.object({
clothingSelectionAbnormalId: z.number().optional().nullable(),
upperClothingActionAbnormalId: z.number().optional().nullable(),
lowerClothingActionAbnormalId: z.number().optional().nullable(),
socksShoesActionAbnormalId: z.number().optional().nullable(),
oralCareActionAbnormalId: z.number().optional().nullable(),
faceWashingActionAbnormalId: z.number().optional().nullable(),
hairdressingActionAbnormalId: z.number().optional().nullable(),
}),
bathing: z.object({
bodyWashingActionAbnormalId: z.number().optional().nullable(),
}),
basicAction: z.object({
maintainingSittingPositionAbnormalId: z.number().optional().nullable(),
standingActionAbnormalId: z.number().optional().nullable(),
maintainingStandingPositionAbnormalId: z.number().optional().nullable(),
}),
content: z.string().optional().nullable(),
images: z.array(z.any()).optional(),
deleteImageIds: z.array(z.string()).optional(),
});

3. アクション層の更新

ファイル: apps/blackswan-web/src/sections/info-sharing/shift-report/actions/shift-report-[feature]-action.ts

registerShiftReport[Feature]Action 関数内のフィールドマッピングを更新します。

変更前:

clothing: {
clothingSelectionId: data.clothing?.clothingSelectionId
? Number(data.clothing.clothingSelectionId)
: null,
upperClothingActionId: data.clothing?.upperClothingActionId
? Number(data.clothing.upperClothingActionId)
: null,
// ... 他のフィールド
},
bathing: {
bodyWashingActionId: data.bathing?.bodyWashingActionId
? Number(data.bathing.bodyWashingActionId)
: null,
},
basicAction: {
maintainingSittingPositionId: data.basicAction?.maintainingSittingPositionId
? Number(data.basicAction.maintainingSittingPositionId)
: null,
standingActionId: data.basicAction?.standingActionId
? Number(data.basicAction.standingActionId)
: null,
maintainingStandingPositionId: data.basicAction?.maintainingStandingPositionId
? Number(data.basicAction.maintainingStandingPositionId)
: null,
},

変更後:

clothing: {
clothingSelectionAbnormalId: data.clothing?.clothingSelectionAbnormalId
? Number(data.clothing.clothingSelectionAbnormalId)
: null,
upperClothingActionAbnormalId: data.clothing?.upperClothingActionAbnormalId
? Number(data.clothing.upperClothingActionAbnormalId)
: null,
lowerClothingActionAbnormalId: data.clothing?.lowerClothingActionAbnormalId
? Number(data.clothing.lowerClothingActionAbnormalId)
: null,
socksShoesActionAbnormalId: data.clothing?.socksShoesActionAbnormalId
? Number(data.clothing.socksShoesActionAbnormalId)
: null,
oralCareActionAbnormalId: data.clothing?.oralCareActionAbnormalId
? Number(data.clothing.oralCareActionAbnormalId)
: null,
faceWashingActionAbnormalId: data.clothing?.faceWashingActionAbnormalId
? Number(data.clothing.faceWashingActionAbnormalId)
: null,
hairdressingActionAbnormalId: data.clothing?.hairdressingActionAbnormalId
? Number(data.clothing.hairdressingActionAbnormalId)
: null,
},
bathing: {
bodyWashingActionAbnormalId: data.bathing?.bodyWashingActionAbnormalId
? Number(data.bathing.bodyWashingActionAbnormalId)
: null,
},
basicAction: {
maintainingSittingPositionAbnormalId: data.basicAction?.maintainingSittingPositionAbnormalId
? Number(data.basicAction.maintainingSittingPositionAbnormalId)
: null,
standingActionAbnormalId: data.basicAction?.standingActionAbnormalId
? Number(data.basicAction.standingActionAbnormalId)
: null,
maintainingStandingPositionAbnormalId: data.basicAction?.maintainingStandingPositionAbnormalId
? Number(data.basicAction.maintainingStandingPositionAbnormalId)
: null,
},

4. ユーティリティ関数の更新

ファイル: apps/blackswan-web/src/sections/info-sharing/shift-report/utils/report-categories/[feature].ts

ステップ 4.1: デフォルト値の更新

変更前:

export const [feature]DefaultValues = {
...basicDefaultValues,
// ... 既存フィールド
clothing: {
clothingSelectionId: null,
upperClothingActionId: null,
lowerClothingActionId: null,
socksShoesActionId: null,
oralCareActionId: null,
faceWashingActionId: null,
hairdressingActionId: null,
},
bathing: {
bodyWashingActionId: null,
},
basicAction: {
maintainingSittingPositionId: null,
standingActionId: null,
maintainingStandingPositionId: null,
},
images: [],
deleteImageIds: [],
};

変更後:

export const [feature]DefaultValues = {
...basicDefaultValues,
// ... 既存フィールド
clothing: {
clothingSelectionAbnormalId: null,
upperClothingActionAbnormalId: null,
lowerClothingActionAbnormalId: null,
socksShoesActionAbnormalId: null,
oralCareActionAbnormalId: null,
faceWashingActionAbnormalId: null,
hairdressingActionAbnormalId: null,
},
bathing: {
bodyWashingActionAbnormalId: null,
},
basicAction: {
maintainingSittingPositionAbnormalId: null,
standingActionAbnormalId: null,
maintainingStandingPositionAbnormalId: null,
},
images: [],
deleteImageIds: [],
};

ステップ 4.2: データ抽出関数の更新

変更前:

export const extract[Feature]Data = (reportData: ReportData) => {
// ... 既存処理

if (reportData.clothing) {
extractedData.clothing = {
clothingSelectionId: reportData.clothing.clothingSelection?.id,
upperClothingActionId: reportData.clothing.upperClothingAction?.id,
lowerClothingActionId: reportData.clothing.lowerClothingAction?.id,
socksShoesActionId: reportData.clothing.socksShoesAction?.id,
oralCareActionId: reportData.clothing.oralCareAction?.id,
faceWashingActionId: reportData.clothing.faceWashingAction?.id,
hairdressingActionId: reportData.clothing.hairdressingAction?.id,
};
}

if (reportData.bathing) {
extractedData.bathing = {
bodyWashingActionId: reportData.bathing.bodyWashingAction?.id,
};
}

if (reportData.basicAction) {
extractedData.basicAction = {
maintainingSittingPositionId: reportData.basicAction.maintainingSittingPosition?.id,
standingActionId: reportData.basicAction.standingAction?.id,
maintainingStandingPositionId: reportData.basicAction.maintainingStandingPosition?.id,
};
}

return extractedData;
};

変更後:

export const extract[Feature]Data = (reportData: ReportData) => {
// ... 既存処理

if (reportData.clothing) {
extractedData.clothing = {
clothingSelectionAbnormalId: reportData.clothing.clothingSelectionAbnormal?.id,
upperClothingActionAbnormalId: reportData.clothing.upperClothingActionAbnormal?.id,
lowerClothingActionAbnormalId: reportData.clothing.lowerClothingActionAbnormal?.id,
socksShoesActionAbnormalId: reportData.clothing.socksShoesActionAbnormal?.id,
oralCareActionAbnormalId: reportData.clothing.oralCareActionAbnormal?.id,
faceWashingActionAbnormalId: reportData.clothing.faceWashingActionAbnormal?.id,
hairdressingActionAbnormalId: reportData.clothing.hairdressingActionAbnormal?.id,
};
}

if (reportData.bathing) {
extractedData.bathing = {
bodyWashingActionAbnormalId: reportData.bathing.bodyWashingActionAbnormal?.id,
};
}

if (reportData.basicAction) {
extractedData.basicAction = {
maintainingSittingPositionAbnormalId: reportData.basicAction.maintainingSittingPositionAbnormal?.id,
standingActionAbnormalId: reportData.basicAction.standingActionAbnormal?.id,
maintainingStandingPositionAbnormalId: reportData.basicAction.maintainingStandingPositionAbnormal?.id,
};
}

return extractedData;
};

5. テーブルコンポーネントの更新(該当する場合)

ファイル: apps/blackswan-web/src/components/shift-report-table/shift-report-table.tsx

mockData のフィールド名を更新します。

変更前:

const mockData: MockDataItem[] = [
{
category: '基本動作',
itemName: '座位保持',
normalValue: '',
fieldName: 'basicAction.maintainingSittingPositionId',
optionsKey: 'listMaintainingSittingPositionResolver',
},
// ... 他の項目
];

変更後:

const mockData: MockDataItem[] = [
{
category: '基本動作',
itemName: '座位保持',
normalValue: '',
fieldName: 'basicAction.maintainingSittingPositionAbnormalId',
optionsKey: 'listMaintainingSittingPositionResolver',
},
{
category: '基本動作',
itemName: '立位動作',
normalValue: '',
fieldName: 'basicAction.standingActionAbnormalId',
optionsKey: 'listStandingActionResolver',
},
{
category: '基本動作',
itemName: '立位保持',
normalValue: '',
fieldName: 'basicAction.maintainingStandingPositionAbnormalId',
optionsKey: 'listMaintainingStandingPositionResolver',
},
{
category: '更衣・整容',
itemName: '服の選択',
normalValue: '',
fieldName: 'clothing.clothingSelectionAbnormalId',
optionsKey: 'listClothingSelectionResolver',
},
{
category: '更衣・整容',
itemName: '上衣更衣動作',
normalValue: '',
fieldName: 'clothing.upperClothingActionAbnormalId',
optionsKey: 'listUpperClothingActionResolver',
},
{
category: '更衣・整容',
itemName: '下衣更衣動作',
normalValue: '',
fieldName: 'clothing.lowerClothingActionAbnormalId',
optionsKey: 'listLowerClothingActionResolver',
},
{
category: '更衣・整容',
itemName: '靴下着脱動作',
normalValue: '',
fieldName: 'clothing.socksShoesActionAbnormalId',
optionsKey: 'listSocksShoesActionResolver',
},
{
category: '更衣・整容',
itemName: '口腔ケア',
normalValue: '',
fieldName: 'clothing.oralCareActionAbnormalId',
optionsKey: 'listOralCareActionResolver',
},
{
category: '更衣・整容',
itemName: '洗顔動作',
normalValue: '',
fieldName: 'clothing.faceWashingActionAbnormalId',
optionsKey: 'listFaceBodyWashingActionResolver',
},
{
category: '更衣・整容',
itemName: '整髪動作',
normalValue: '',
fieldName: 'clothing.hairdressingActionAbnormalId',
optionsKey: 'listHairdressingActionResolver',
},
{
category: '入浴',
itemName: '洗身動作',
normalValue: '',
fieldName: 'bathing.bodyWashingActionAbnormalId',
optionsKey: 'listBodyWashingActionResolver',
},
];

6. GraphQL 型生成(codegen)の実行

cd apps/blackswan-web
npm run codegen

完全な修正チェックリスト

バックエンド

  • schema.prisma の更新
  • マイグレーションファイルの生成と編集(3ステップ)
  • エンティティファイルの更新
  • リポジトリの更新(Input型、Prisma入力、Return文)
  • サービス層の更新(Input型)
  • BFF 型定義の更新(InputType は Abnormal のみ、ObjectType は両方)
  • BFF サービス層の更新(Normal値の自動取得ロジック)
  • BFF レスポンス変換メソッドの更新(4つすべて)
  • getDefaultShiftReport[Feature] メソッドの修正(Normal値のみ、Abnormal値は全てnull)
  • ユーティリティ関数の更新(必要な場合)
  • BFF モジュールの更新(依存サービスの追加)
  • npx prisma migrate dev の実行
  • npm run build:docs の実行

フロントエンド

  • GraphQL クエリファイルの更新(4ファイル)
  • バリデーションスキーマの更新
  • アクション層の更新
  • ユーティリティ関数の更新(デフォルト値とデータ抽出)
  • テーブルコンポーネントの更新(該当する場合)
  • npm run codegen の実行

トラブルシューティング

型エラーが発生する場合

  1. バックエンドで npm run build:docs が正常に完了しているか確認
  2. フロントエンドで npm run codegen を実行
  3. 両方のサーバーを再起動

マイグレーションエラーが発生する場合

  1. マイグレーションファイルの SQL 構文を確認
  2. 既存データの移行ロジック(Step 2)が正しいか確認
  3. 必要に応じて npx prisma migrate reset でリセット

データが正しく表示されない場合

  1. BFF サービス層の Normal 値取得ロジックを確認
  2. レスポンス変換メソッドで Normal/Abnormal 両方をマッピングしているか確認
  3. フロントエンドの GraphQL クエリで Normal/Abnormal フィールドを両方取得しているか確認

参考実装

実際の実装例として、以下のファイルを参照してください:

バックエンド:

  • packages/backend/src/bff/native/shift-report-page/shift-report-grooming-page/
  • packages/backend/src/modules/shift-report-grooming/

フロントエンド:

  • apps/blackswan-web/src/graphql/shift-report/grooming/
  • apps/blackswan-web/src/sections/info-sharing/shift-report/

データソースリファレンス

Normal 値のデータソースについては、docs/backend/normal_abnormal_values.md を参照してください。


このドキュメントに従うことで、他の申し送り機能にも同様の Normal/Abnormal フィールドを追加できます。