diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index 09d67f4..f6b6ff7 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -54,4 +54,5 @@ export const ErrorCodes = [ 'E010908', // タイピストグループ不在エラー 'E011001', // ワークタイプ重複エラー 'E011002', // ワークタイプ登録上限超過エラー + 'E011003', // ワークタイプ不在エラー ] as const; diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index 7515d95..acd2efd 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -43,4 +43,5 @@ export const errors: Errors = { E010908: 'Typist Group not exist Error', E011001: 'Thiw WorkTypeID already used Error', E011002: 'WorkTypeID create limit exceeded Error', + E011003: 'WorkTypeID not found Error', }; diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index 9753144..93abc48 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -759,10 +759,13 @@ export class AccountsController { const context = makeContext(userId); - console.log('worktypeId: ', worktypeId); - console.log('description: ', description); - console.log('id: ', id); - console.log(context.trackingId); + await this.accountService.updateWorktype( + context, + userId, + id, + worktypeId, + description, + ); return {}; } diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index b8ab69c..618abf9 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -61,6 +61,7 @@ import { selectOrderLicense, } from '../licenses/test/utility'; import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service'; +import { Worktype } from '../../repositories/worktypes/entity/worktype.entity'; describe('createAccount', () => { let source: DataSource = null; @@ -3591,6 +3592,275 @@ describe('createWorktype', () => { } }); }); + +describe('updateWorktype', () => { + let source: DataSource = null; + beforeEach(async () => { + source = new DataSource({ + type: 'sqlite', + database: ':memory:', + logging: false, + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: true, // trueにすると自動的にmigrationが行われるため注意 + }); + return source.initialize(); + }); + + afterEach(async () => { + await source.destroy(); + source = null; + }); + + it('Worktypeを更新できる', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + + const service = module.get(AccountsService); + const context = makeContext(admin.external_id); + + const worktype = new Worktype(); + worktype.custom_worktype_id = 'worktypeID1'; + worktype.description = 'description1'; + + await createWorktype( + source, + account.id, + worktype.custom_worktype_id, + worktype.description, + ); + + // Worktypeを確認 + const worktypes = await getWorktypes(source, account.id); + { + expect(worktypes.length).toBe(1); + expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id); + expect(worktypes[0].description).toBe(worktype.description); + } + + const updateWorktypeId = 'updateWorktypeID'; + const updateDescription = 'updateDescription'; + + await service.updateWorktype( + context, + admin.external_id, + worktypes[0].id, + updateWorktypeId, + updateDescription, + ); + + //実行結果を確認 + { + const worktypes = await getWorktypes(source, account.id); + expect(worktypes.length).toBe(1); + expect(worktypes[0].custom_worktype_id).toBe(updateWorktypeId); + expect(worktypes[0].description).toBe(updateDescription); + } + }); + + it('指定したIDが登録されていない場合、400エラーとなること', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + + const service = module.get(AccountsService); + const context = makeContext(admin.external_id); + + const worktype = new Worktype(); + worktype.custom_worktype_id = 'worktypeID1'; + worktype.description = 'description1'; + + await createWorktype( + source, + account.id, + worktype.custom_worktype_id, + worktype.description, + ); + + // Worktypeを確認 + { + const worktypes = await getWorktypes(source, account.id); + expect(worktypes.length).toBe(1); + expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id); + expect(worktypes[0].description).toBe(worktype.description); + } + + try { + await service.updateWorktype( + context, + admin.external_id, + 999, + 'newWorktypeID', + 'newDescription', + ); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E011003')); + } else { + fail(); + } + } + }); + + it('WorktypeIDが登録済みのWorktypeIDと重複した場合、400エラーとなること', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + + const service = module.get(AccountsService); + const context = makeContext(admin.external_id); + const worktype1 = new Worktype(); + worktype1.custom_worktype_id = 'worktypeID1'; + worktype1.description = 'description1'; + + const worktype2 = new Worktype(); + worktype2.custom_worktype_id = 'worktypeID2'; + worktype2.description = 'description2'; + + await createWorktype( + source, + account.id, + worktype1.custom_worktype_id, + worktype1.description, + ); + await createWorktype( + source, + account.id, + worktype2.custom_worktype_id, + worktype2.description, + ); + + //作成したデータを確認 + const worktypes = await getWorktypes(source, account.id); + { + expect(worktypes.length).toBe(2); + expect(worktypes[0].custom_worktype_id).toBe( + worktype1.custom_worktype_id, + ); + expect(worktypes[0].description).toBe(worktype1.description); + expect(worktypes[1].custom_worktype_id).toBe( + worktype2.custom_worktype_id, + ); + expect(worktypes[1].description).toBe(worktype2.description); + } + + try { + await service.updateWorktype( + context, + admin.external_id, + worktypes[0].id, + worktype2.custom_worktype_id, + worktype2.description, + ); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E011001')); + } else { + fail(); + } + } + }); + + it('WorktypeIDが登録済みの指定IDのWorktypeIDと重複した場合でも更新できること', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + + const service = module.get(AccountsService); + const context = makeContext(admin.external_id); + + const worktype = new Worktype(); + worktype.custom_worktype_id = 'worktypeID1'; + worktype.description = 'description1'; + + await createWorktype( + source, + account.id, + worktype.custom_worktype_id, + worktype.description, + ); + + // Worktypeを確認 + const worktypes = await getWorktypes(source, account.id); + { + expect(worktypes.length).toBe(1); + expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id); + expect(worktypes[0].description).toBe(worktype.description); + } + + const updateDescription = 'updateDescription'; + + await service.updateWorktype( + context, + admin.external_id, + worktypes[0].id, + worktype.custom_worktype_id, + updateDescription, + ); + + //実行結果を確認 + { + const worktypes = await getWorktypes(source, account.id); + expect(worktypes.length).toBe(1); + expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id); + expect(worktypes[0].description).toBe(updateDescription); + } + }); + + it('DBアクセスに失敗した場合、500エラーを返却する', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { account, admin } = await makeTestAccount(source, { tier: 5 }); + + const service = module.get(AccountsService); + const context = makeContext(admin.external_id); + + const worktype = new Worktype(); + worktype.custom_worktype_id = 'worktypeID1'; + worktype.description = 'description1'; + + await createWorktype( + source, + account.id, + worktype.custom_worktype_id, + worktype.description, + ); + + // Worktypeを確認 + const worktypes = await getWorktypes(source, account.id); + { + expect(worktypes.length).toBe(1); + expect(worktypes[0].custom_worktype_id).toBe(worktype.custom_worktype_id); + expect(worktypes[0].description).toBe(worktype.description); + } + + //DBアクセスに失敗するようにする + const worktypeService = module.get( + WorktypesRepositoryService, + ); + worktypeService.updateWorktype = jest.fn().mockRejectedValue('DB failed'); + + try { + await service.updateWorktype( + context, + admin.external_id, + worktypes[0].id, + 'newWorktype', + 'newDescription', + ); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); + } else { + fail(); + } + } + }); +}); + describe('ライセンス発行キャンセル', () => { let source: DataSource = null; beforeEach(async () => { diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 1c9e1ca..bdfb545 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -53,6 +53,7 @@ import { WorktypesRepositoryService } from '../../repositories/worktypes/worktyp import { WorktypeIdAlreadyExistsError, WorktypeIdMaxCountError, + WorktypeIdNotFoundError, } from '../../repositories/worktypes/errors/types'; @Injectable() @@ -1241,4 +1242,74 @@ export class AccountsService { ); } } + + /** + * ワークタイプを更新します + * @param context + * @param externalId + * @param id ワークタイプの内部ID + * @param worktypeId ユーザーが設定するワークタイプ名 + * @param [description] + * @returns worktype + */ + async updateWorktype( + context: Context, + externalId: string, + id: number, + worktypeId: string, + description?: string, + ): Promise { + this.logger.log( + `[IN] [${context.trackingId}] ${this.updateWorktype.name} | params: { ` + + `externalId: ${externalId}, ` + + `id: ${id}, ` + + `worktypeId: ${worktypeId}, ` + + `description: ${description} };`, + ); + + try { + // 外部IDをもとにユーザー情報を取得する + const { account_id: accountId } = + await this.usersRepository.findUserByExternalId(externalId); + + // ワークタイプを更新する + await this.worktypesRepository.updateWorktype( + accountId, + id, + worktypeId, + description, + ); + } catch (e) { + this.logger.error(`error=${e}`); + if (e instanceof Error) { + switch (e.constructor) { + // ユーザーが設定したWorktypeIDが既存WorktypeのWorktypeIDと重複する場合は400エラーを返す + case WorktypeIdAlreadyExistsError: + throw new HttpException( + makeErrorResponse('E011001'), + HttpStatus.BAD_REQUEST, + ); + // 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す + case WorktypeIdNotFoundError: + throw new HttpException( + makeErrorResponse('E011003'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log( + `[OUT] [${context.trackingId}] ${this.updateWorktype.name}`, + ); + } + } } diff --git a/dictation_server/src/repositories/worktypes/errors/types.ts b/dictation_server/src/repositories/worktypes/errors/types.ts index eefc9e0..af772a1 100644 --- a/dictation_server/src/repositories/worktypes/errors/types.ts +++ b/dictation_server/src/repositories/worktypes/errors/types.ts @@ -2,3 +2,5 @@ export class WorktypeIdAlreadyExistsError extends Error {} // WorktypeID登録上限エラー export class WorktypeIdMaxCountError extends Error {} +// WorktypeID不在エラー +export class WorktypeIdNotFoundError extends Error {} diff --git a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts index d8ca2cc..2f3920c 100644 --- a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts +++ b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { DataSource } from 'typeorm'; +import { DataSource, Not } from 'typeorm'; import { Worktype } from './entity/worktype.entity'; import { OPTION_ITEM_NUM, @@ -9,6 +9,7 @@ import { import { WorktypeIdAlreadyExistsError, WorktypeIdMaxCountError, + WorktypeIdNotFoundError, } from './errors/types'; import { OptionItem } from '../option_items/entity/option_item.entity'; @@ -34,14 +35,13 @@ export class WorktypesRepositoryService { * @param accountId * @param worktypeId * @param [description] - * @returns worktype */ async createWorktype( accountId: number, worktypeId: string, description?: string, - ): Promise { - return await this.dataSource.transaction(async (entityManager) => { + ): Promise { + await this.dataSource.transaction(async (entityManager) => { const worktypeRepo = entityManager.getRepository(Worktype); const optionItemRepo = entityManager.getRepository(OptionItem); @@ -86,8 +86,53 @@ export class WorktypesRepositoryService { }); await optionItemRepo.save(newOptionItems); + }); + } - return worktype; + /** + * ワークタイプを更新する + * @param accountId + * @param id ワークタイプの内部ID + * @param worktypeId ユーザーが設定するワークタイプ名 + * @param [description] + */ + async updateWorktype( + accountId: number, + id: number, + worktypeId: string, + description?: string, + ): Promise { + await this.dataSource.transaction(async (entityManager) => { + const worktypeRepo = entityManager.getRepository(Worktype); + + const worktype = await worktypeRepo.findOne({ + where: { account_id: accountId, id: id }, + }); + + // ワークタイプが存在しない場合はエラー + if (!worktype) { + throw new WorktypeIdNotFoundError(`Worktype is not found. id: ${id}`); + } + + const duplicatedWorktype = await worktypeRepo.findOne({ + where: { + account_id: accountId, + custom_worktype_id: worktypeId, + id: Not(id), + }, + }); + + // ワークタイプIDが重複している場合はエラー + if (duplicatedWorktype) { + throw new WorktypeIdAlreadyExistsError( + `WorktypeID is already exists. WorktypeID: ${worktypeId}`, + ); + } + + // ワークタイプを更新 + worktype.custom_worktype_id = worktypeId; + worktype.description = description; + await worktypeRepo.save(worktype); }); } }