From 0b7d979faedc54dd62c81bacfd8c4cb9108f9355 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Mon, 4 Sep 2023 07:08:19 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20375:=20API=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=EF=BC=88=E3=83=AF=E3=83=BC=E3=82=AF=E3=82=BF=E3=82=A4=E3=83=97?= =?UTF-8?q?ID=E8=BF=BD=E5=8A=A0API=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2516: API実装(ワークタイプID追加API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2516) - ワークタイプ追加APIとテストを実装しました。 - オプションアイテムも一緒に追加されるように実装しています。 - ワークタイプの制限のためにカスタムバリデータを実装しています。 ## レビューポイント - 追加時のエラー処理は適切か - バリデータは適切か ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- dictation_server/src/app.module.ts | 2 + dictation_server/src/common/error/code.ts | 2 + dictation_server/src/common/error/message.ts | 2 + dictation_server/src/common/test/modules.ts | 2 + .../common/validators/worktype.validator.ts | 50 ++++++ dictation_server/src/constants/index.ts | 15 ++ .../features/accounts/accounts.controller.ts | 9 +- .../accounts/accounts.service.spec.ts | 153 +++++++++++++++++- .../src/features/accounts/accounts.service.ts | 63 ++++++++ .../src/features/accounts/test/utility.ts | 15 ++ .../src/features/accounts/types/types.ts | 5 + .../option_items/entity/option_item.entity.ts | 29 ++++ .../option_items.repository.module.ts | 11 ++ .../option_items.repository.service.ts | 7 + .../repositories/worktypes/errors/types.ts | 5 +- .../worktypes/worktypes.repository.service.ts | 71 ++++++++ 16 files changed, 436 insertions(+), 5 deletions(-) create mode 100644 dictation_server/src/common/validators/worktype.validator.ts create mode 100644 dictation_server/src/repositories/option_items/entity/option_item.entity.ts create mode 100644 dictation_server/src/repositories/option_items/option_items.repository.module.ts create mode 100644 dictation_server/src/repositories/option_items/option_items.repository.service.ts diff --git a/dictation_server/src/app.module.ts b/dictation_server/src/app.module.ts index a5cd069..e8b4375 100644 --- a/dictation_server/src/app.module.ts +++ b/dictation_server/src/app.module.ts @@ -41,6 +41,7 @@ import { UserGroupsRepositoryModule } from './repositories/user_groups/user_grou import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_criteria.repository.module'; import { TemplateFilesRepositoryModule } from './repositories/template_files/template_files.repository.module'; import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.repository.module'; +import { OptionItemsRepositoryModule } from './repositories/option_items/option_items.repository.module'; @Module({ imports: [ @@ -96,6 +97,7 @@ import { WorktypesRepositoryModule } from './repositories/worktypes/worktypes.re AuthGuardsModule, SortCriteriaRepositoryModule, WorktypesRepositoryModule, + OptionItemsRepositoryModule, ], controllers: [ HealthController, diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index 7692f36..f8480b0 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -49,4 +49,6 @@ export const ErrorCodes = [ 'E010807', // ライセンス割り当て解除済みエラー 'E010808', // ライセンス注文キャンセル不可エラー 'E010908', // タイピストグループ不在エラー + 'E011001', // ワークタイプ重複エラー + 'E011002', // ワークタイプ登録上限超過エラー ] as const; diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index 5b2338b..d11aafb 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -38,4 +38,6 @@ export const errors: Errors = { E010807: 'License is already deallocated Error', E010808: 'Order cancel failed Error', E010908: 'Typist Group not exist Error', + E011001: 'Thiw WorkTypeID already used Error', + E011002: 'WorkTypeID create limit exceeded Error', }; diff --git a/dictation_server/src/common/test/modules.ts b/dictation_server/src/common/test/modules.ts index ed77ffc..5788870 100644 --- a/dictation_server/src/common/test/modules.ts +++ b/dictation_server/src/common/test/modules.ts @@ -30,6 +30,7 @@ import { NotificationhubService } from '../../gateways/notificationhub/notificat import { FilesService } from '../../features/files/files.service'; import { LicensesService } from '../../features/licenses/licenses.service'; import { TasksService } from '../../features/tasks/tasks.service'; +import { OptionItemsRepositoryModule } from '../../repositories/option_items/option_items.repository.module'; export const makeTestingModule = async ( datasource: DataSource, @@ -65,6 +66,7 @@ export const makeTestingModule = async ( AuthGuardsModule, SortCriteriaRepositoryModule, WorktypesRepositoryModule, + OptionItemsRepositoryModule, ], providers: [ AuthService, diff --git a/dictation_server/src/common/validators/worktype.validator.ts b/dictation_server/src/common/validators/worktype.validator.ts new file mode 100644 index 0000000..e6b9c9c --- /dev/null +++ b/dictation_server/src/common/validators/worktype.validator.ts @@ -0,0 +1,50 @@ +import { + ValidatorConstraint, + ValidatorConstraintInterface, + ValidationArguments, + ValidationOptions, + registerDecorator, +} from 'class-validator'; + +@ValidatorConstraint() +export class IsWorktypeIdCharacters implements ValidatorConstraintInterface { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + validate(value: string) { + // 正規表現でWorktypeIDのチェックを行う + // 以下の禁則文字を除く半角英数記号 + // \ (backslash) + // / (forward slash) + // : (colon) + // * (asterisk) + // ? (question mark) + // " (double quotation mark) + // < (less-than symbol) + // > (greater-than symbol) + // | (vertical bar) + // . (period) + const regex = + /^(?!.*\\)(?!.*\/)(?!.*:)(?!.*\*)(?!.*\?)(?!.*")(?!.*<)(?!.*>)(?!.*\|)(?!.*\.)[ -~]+$/; + return regex.test(value); + } + + defaultMessage(args: ValidationArguments) { + return `WorktypeID rule not satisfied`; + } +} +/** + * WorktypeIDで使用できる文字列かをチェックする + * @param [validationOptions] + * @returns + */ +export function IsWorktypeId(validationOptions?: ValidationOptions) { + return function (object: object, propertyName: string) { + registerDecorator({ + name: 'IsWorktypeId', + target: object.constructor, + propertyName: propertyName, + constraints: [], + options: validationOptions, + validator: IsWorktypeIdCharacters, + }); + }; +} diff --git a/dictation_server/src/constants/index.ts b/dictation_server/src/constants/index.ts index 98bd997..ca38ec5 100644 --- a/dictation_server/src/constants/index.ts +++ b/dictation_server/src/constants/index.ts @@ -223,3 +223,18 @@ export const TRIAL_LICENSE_EXPIRATION_DAYS = 30; * @const {number} */ export const TRIAL_LICENSE_ISSUE_NUM = 100; + +/** + * worktypeの最大登録数 + * @const {number} + */ +export const WORKTYPE_MAX_COUNT = 20; + +/** + * worktypeのDefault値の取りうる値 + **/ +export const OPTION_ITEM_VALUE_TYPE = { + DEFAULT: 'Default', + BLANK: 'Blank', + LAST_INPUT: 'LastInput', +} as const; diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index b77c935..94cbf6d 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -706,9 +706,12 @@ export class AccountsController { const { userId } = jwt.decode(token, { json: true }) as AccessToken; const context = makeContext(userId); - console.log(context.trackingId); - console.log(worktypeId); - console.log(description); + await this.accountService.createWorktype( + context, + userId, + 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 ba206d0..d6f5f0e 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -17,6 +17,7 @@ import { createLicenseOrder, createLicenseSetExpiryDateAndStatus, createWorktype, + getOptionItems, getSortCriteria, getTypistGroup, getTypistGroupMember, @@ -34,7 +35,12 @@ import { } from '../../common/test/utility'; import { AccountsService } from './accounts.service'; import { Context, makeContext } from '../../common/log'; -import { TIERS, USER_ROLES } from '../../constants'; +import { + OPTION_ITEM_VALUE_TYPE, + TIERS, + USER_ROLES, + WORKTYPE_MAX_COUNT, +} from '../../constants'; import { License } from '../../repositories/licenses/entity/license.entity'; import { overrideAccountsRepositoryService, @@ -46,6 +52,7 @@ import { AdB2cService } from '../../gateways/adb2c/adb2c.service'; import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service'; import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service'; import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service'; +import exp from 'constants'; describe('createAccount', () => { let source: DataSource = null; @@ -3263,3 +3270,147 @@ describe('getWorktypes', () => { } }); }); + +describe('createWorktype', () => { + 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); + + // Worktypeが未登録であることを確認 + { + const worktypes = await getWorktypes(source, account.id); + const optionItems = await getOptionItems(source); + expect(worktypes.length).toBe(0); + expect(optionItems.length).toBe(0); + } + + await service.createWorktype( + context, + admin.external_id, + 'worktype1', + 'description1', + ); + + //実行結果を確認 + { + const worktypes = await getWorktypes(source, account.id); + const optionItems = await getOptionItems(source, worktypes[0].id); + expect(worktypes.length).toBe(1); + expect(worktypes[0].custom_worktype_id).toBe('worktype1'); + expect(worktypes[0].description).toBe('description1'); + expect(optionItems.length).toBe(10); + expect(optionItems[0].item_label).toBe(''); + expect(optionItems[0].default_value_type).toBe( + OPTION_ITEM_VALUE_TYPE.DEFAULT, + ); + expect(optionItems[0].initial_value).toBe(''); + } + }); + + 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 worktypeId = 'worktype1'; + await createWorktype(source, account.id, worktypeId); + + //作成したデータを確認 + { + const worktypes = await getWorktypes(source, account.id); + expect(worktypes.length).toBe(1); + expect(worktypes[0].custom_worktype_id).toBe(worktypeId); + } + + try { + await service.createWorktype(context, admin.external_id, worktypeId); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E011001')); + } else { + fail(); + } + } + }); + + it('WorktypeIDがすでに最大登録数(20件)まで登録されている場合、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); + + // あらかじめ最大登録数分のWorktypeを登録する + for (let i = 0; i < WORKTYPE_MAX_COUNT; i++) { + await createWorktype(source, account.id, `worktype${i + 1}`); + } + + //作成したデータを確認 + { + const worktypes = await getWorktypes(source, account.id); + expect(worktypes.length).toBe(WORKTYPE_MAX_COUNT); + } + + try { + await service.createWorktype(context, admin.external_id, 'newWorktypeID'); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E011002')); + } else { + fail(); + } + } + }); + + it('DBアクセスに失敗した場合、500エラーを返却する', async () => { + const module = await makeTestingModule(source); + // 第五階層のアカウント作成 + const { admin } = await makeTestAccount(source, { tier: 5 }); + + const service = module.get(AccountsService); + const context = makeContext(admin.external_id); + + //DBアクセスに失敗するようにする + const worktypeService = module.get( + WorktypesRepositoryService, + ); + worktypeService.createWorktype = jest.fn().mockRejectedValue('DB failed'); + + try { + await service.createWorktype(context, admin.external_id, 'worktype1'); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR); + expect(e.getResponse()).toEqual(makeErrorResponse('E009999')); + } else { + fail(); + } + } + }); +}); diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 4aa8f2c..b350a25 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -47,6 +47,10 @@ import { TypistIdInvalidError, } from '../../repositories/user_groups/errors/types'; import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service'; +import { + WorktypeIdAlreadyExistsError, + WorktypeIdMaxCountError, +} from '../../repositories/worktypes/errors/types'; @Injectable() export class AccountsService { @@ -1091,4 +1095,63 @@ export class AccountsService { ); } } + /** + * ワークタイプを作成します + * @param context + * @param externalId + * @param worktypeId + * @param [description] + * @returns worktype + */ + async createWorktype( + context: Context, + externalId: string, + worktypeId: string, + description?: string, + ): Promise { + this.logger.log(`[IN] [${context.trackingId}] ${this.createWorktype.name}`); + + try { + // 外部IDをもとにユーザー情報を取得する + const { account_id: accountId } = + await this.usersRepository.findUserByExternalId(externalId); + + await this.worktypesRepository.createWorktype( + accountId, + worktypeId, + description, + ); + } catch (e) { + this.logger.error(`error=${e}`); + if (e instanceof Error) { + switch (e.constructor) { + // WorktypeIDが既に存在する場合は400エラーを返す + case WorktypeIdAlreadyExistsError: + throw new HttpException( + makeErrorResponse('E011001'), + HttpStatus.BAD_REQUEST, + ); + // WorktypeIDが登録上限以上の場合は400エラーを返す + case WorktypeIdMaxCountError: + throw new HttpException( + makeErrorResponse('E011002'), + 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.createWorktype.name}`, + ); + } + } } diff --git a/dictation_server/src/features/accounts/test/utility.ts b/dictation_server/src/features/accounts/test/utility.ts index 1410c68..e110397 100644 --- a/dictation_server/src/features/accounts/test/utility.ts +++ b/dictation_server/src/features/accounts/test/utility.ts @@ -7,6 +7,7 @@ import { SortCriteria } from '../../../repositories/sort_criteria/entity/sort_cr import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity'; import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity'; import { Worktype } from '../../../repositories/worktypes/entity/worktype.entity'; +import { OptionItem } from '../../../repositories/option_items/entity/option_item.entity'; /** * テスト ユーティリティ: すべてのソート条件を取得する @@ -142,3 +143,17 @@ export const getWorktypes = async ( }, }); }; + +// オプションアイテムを取得する +export const getOptionItems = async ( + datasource: DataSource, + worktypeId?: number, +): Promise => { + return worktypeId + ? await datasource.getRepository(OptionItem).find({ + where: { + worktype_id: worktypeId, + }, + }) + : await datasource.getRepository(OptionItem).find(); +}; diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index 4ad6f6c..a828112 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -13,6 +13,7 @@ import { import { IsAdminPasswordvalid } from '../../../common/validators/admin.validator'; import { IsUnique } from '../../../common/validators/IsUnique.validator'; import { Type } from 'class-transformer'; +import { IsWorktypeId } from '../../../common/validators/worktype.validator'; export class CreateAccountRequest { @ApiProperty() @@ -350,8 +351,12 @@ export class GetWorktypesResponse { export class CreateWorktypesRequest { @ApiProperty({ minLength: 1, description: 'WorktypeID' }) @MinLength(1) + @MaxLength(255) + @IsWorktypeId() worktypeId: string; @ApiProperty({ description: 'Worktypeの説明', required: false }) + @MaxLength(255) + @IsOptional() description?: string; } diff --git a/dictation_server/src/repositories/option_items/entity/option_item.entity.ts b/dictation_server/src/repositories/option_items/entity/option_item.entity.ts new file mode 100644 index 0000000..fd0a14e --- /dev/null +++ b/dictation_server/src/repositories/option_items/entity/option_item.entity.ts @@ -0,0 +1,29 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + UpdateDateColumn, + CreateDateColumn, +} from 'typeorm'; + +@Entity({ name: 'option_items' }) +export class OptionItem { + @PrimaryGeneratedColumn() + id: number; + @Column() + worktype_id: number; + @Column() + item_label: string; + @Column() + default_value_type: string; + @Column() + initial_value: string; + @Column({ nullable: true }) + created_by?: string; + @CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + created_at?: Date; + @Column({ nullable: true }) + updated_by?: string; + @UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + updated_at?: Date; +} diff --git a/dictation_server/src/repositories/option_items/option_items.repository.module.ts b/dictation_server/src/repositories/option_items/option_items.repository.module.ts new file mode 100644 index 0000000..17cf5f7 --- /dev/null +++ b/dictation_server/src/repositories/option_items/option_items.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { OptionItem } from './entity/option_item.entity'; +import { OptionItemsRepositoryService } from './option_items.repository.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([OptionItem])], + providers: [OptionItemsRepositoryService], + exports: [OptionItemsRepositoryService], +}) +export class OptionItemsRepositoryModule {} diff --git a/dictation_server/src/repositories/option_items/option_items.repository.service.ts b/dictation_server/src/repositories/option_items/option_items.repository.service.ts new file mode 100644 index 0000000..884b068 --- /dev/null +++ b/dictation_server/src/repositories/option_items/option_items.repository.service.ts @@ -0,0 +1,7 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; + +@Injectable() +export class OptionItemsRepositoryService { + constructor(private dataSource: DataSource) {} +} diff --git a/dictation_server/src/repositories/worktypes/errors/types.ts b/dictation_server/src/repositories/worktypes/errors/types.ts index 8b13789..eefc9e0 100644 --- a/dictation_server/src/repositories/worktypes/errors/types.ts +++ b/dictation_server/src/repositories/worktypes/errors/types.ts @@ -1 +1,4 @@ - +// WorktypeID重複エラー +export class WorktypeIdAlreadyExistsError extends Error {} +// WorktypeID登録上限エラー +export class WorktypeIdMaxCountError 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 f5fe450..d8ca2cc 100644 --- a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts +++ b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts @@ -1,6 +1,16 @@ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { Worktype } from './entity/worktype.entity'; +import { + OPTION_ITEM_NUM, + OPTION_ITEM_VALUE_TYPE, + WORKTYPE_MAX_COUNT, +} from '../../constants'; +import { + WorktypeIdAlreadyExistsError, + WorktypeIdMaxCountError, +} from './errors/types'; +import { OptionItem } from '../option_items/entity/option_item.entity'; @Injectable() export class WorktypesRepositoryService { @@ -19,4 +29,65 @@ export class WorktypesRepositoryService { return worktypes; }); } + /** + * ワークタイプを作成する + * @param accountId + * @param worktypeId + * @param [description] + * @returns worktype + */ + async createWorktype( + accountId: number, + worktypeId: string, + description?: string, + ): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const worktypeRepo = entityManager.getRepository(Worktype); + const optionItemRepo = entityManager.getRepository(OptionItem); + + const duplicatedWorktype = await worktypeRepo.findOne({ + where: { account_id: accountId, custom_worktype_id: worktypeId }, + }); + + // ワークタイプIDが重複している場合はエラー + if (duplicatedWorktype) { + throw new WorktypeIdAlreadyExistsError( + `WorktypeID is already exists. WorktypeID: ${worktypeId}`, + ); + } + + const worktypeCount = await worktypeRepo.count({ + where: { account_id: accountId }, + }); + + // ワークタイプの登録数が上限に達している場合はエラー + if (worktypeCount >= WORKTYPE_MAX_COUNT) { + throw new WorktypeIdMaxCountError( + `Number of worktype is exceeded the limit. MAX_COUNT: ${WORKTYPE_MAX_COUNT}, currentCount: ${worktypeCount}`, + ); + } + + // ワークタイプを作成 + const worktype = await worktypeRepo.save({ + account_id: accountId, + custom_worktype_id: worktypeId, + description: description, + }); + + // ワークタイプに紐づくオプションアイテムを10件作成 + const newOptionItems = Array.from({ length: OPTION_ITEM_NUM }, () => { + const optionItem = new OptionItem(); + optionItem.worktype_id = worktype.id; + optionItem.item_label = ''; + optionItem.default_value_type = OPTION_ITEM_VALUE_TYPE.DEFAULT; + optionItem.initial_value = ''; + + return optionItem; + }); + + await optionItemRepo.save(newOptionItems); + + return worktype; + }); + } }