From 84b0da1f956badc42b54618349611a5b5451df1d Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Mon, 5 Feb 2024 11:46:29 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20723:=20[FB=E5=AF=BE=E5=BF=9C]?= =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=94=E3=82=B9=E3=83=88=E3=82=B0=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=83=97=E9=87=8D=E8=A4=87=E6=99=82=E3=81=AE=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=81=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3613: 対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3613) - タイピストグループ名が重複した際のエラーを追加 - タイピストグループ追加API - タイピストグループ更新API - タイピストグループ設定画面に表示するエラーメッセージを追加 ## レビューポイント - 行ロックするべきかどうか - ギリギリのタイミングで同名のタイピストグループが作成される場合は防げないのでDBでユニーク制約を設定する? - insertにロックはかけられないから ## UIの変更 - https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3613?csf=1&web=1&e=i8cN2g ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- dictation_client/src/common/errors/code.ts | 2 ++ .../workflow/typistGroup/operations.ts | 31 +++++++++++----- dictation_client/src/translation/de.json | 5 ++- dictation_client/src/translation/en.json | 7 ++-- dictation_client/src/translation/es.json | 7 ++-- dictation_client/src/translation/fr.json | 7 ++-- .../058-add_user_group_unique_constraint.sql | 6 ++++ dictation_server/src/common/error/code.ts | 1 + dictation_server/src/common/error/message.ts | 1 + .../src/features/accounts/accounts.service.ts | 13 +++++++ .../repositories/user_groups/errors/types.ts | 7 ++++ .../user_groups.repository.service.ts | 36 +++++++++++++++++-- 12 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 dictation_server/db/migrations/058-add_user_group_unique_constraint.sql diff --git a/dictation_client/src/common/errors/code.ts b/dictation_client/src/common/errors/code.ts index da0d9b9..50d91db 100644 --- a/dictation_client/src/common/errors/code.ts +++ b/dictation_client/src/common/errors/code.ts @@ -54,6 +54,8 @@ export const errorCodes = [ "E010809", // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合) "E010810", // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合) "E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合) + "E010908", // タイピストグループ不在エラー + "E010909", // タイピストグループ名重複エラー "E011001", // ワークタイプ重複エラー "E011002", // ワークタイプ登録上限超過エラー "E011003", // ワークタイプ不在エラー diff --git a/dictation_client/src/features/workflow/typistGroup/operations.ts b/dictation_client/src/features/workflow/typistGroup/operations.ts index a98ff1b..70594c6 100644 --- a/dictation_client/src/features/workflow/typistGroup/operations.ts +++ b/dictation_client/src/features/workflow/typistGroup/operations.ts @@ -122,11 +122,17 @@ export const createTypistGroupAsync = createAsyncThunk< } catch (e) { // e ⇒ errorObjectに変換" const error = createErrorObject(e); - - const message = - error.statusCode === 400 - ? getTranslationID("typistGroupSetting.message.groupSaveFailedError") - : getTranslationID("common.message.internalServerError"); + let message = getTranslationID("common.message.internalServerError"); + if (error.code === "E010204") { + message = getTranslationID( + "typistGroupSetting.message.groupSaveFailedError" + ); + } + if (error.code === "E010909") { + message = getTranslationID( + "typistGroupSetting.message.GroupNameAlreadyExistError" + ); + } thunkApi.dispatch( openSnackbar({ @@ -242,10 +248,17 @@ export const updateTypistGroupAsync = createAsyncThunk< // e ⇒ errorObjectに変換" const error = createErrorObject(e); - const message = - error.statusCode === 400 - ? getTranslationID("typistGroupSetting.message.groupSaveFailedError") - : getTranslationID("common.message.internalServerError"); + let message = getTranslationID("common.message.internalServerError"); + if (error.code === "E010204" || error.code === "E010908") { + message = getTranslationID( + "typistGroupSetting.message.groupSaveFailedError" + ); + } + if (error.code === "E010909") { + message = getTranslationID( + "typistGroupSetting.message.GroupNameAlreadyExistError" + ); + } thunkApi.dispatch( openSnackbar({ diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index bb2c373..bff0bba 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -424,7 +424,10 @@ }, "message": { "selectedTypistEmptyError": "Um eine Transkriptionsgruppe zu speichern, müssen ein oder mehrere Transkriptionisten ausgewählt werden.", - "groupSaveFailedError": "Die Transkriptionistengruppe konnte nicht gespeichert werden. Die angezeigten Informationen sind möglicherweise veraltet. Aktualisieren Sie daher bitte den Bildschirm, um den neuesten Status anzuzeigen." + "groupSaveFailedError": "Die Transkriptionistengruppe konnte nicht gespeichert werden. Die angezeigten Informationen sind möglicherweise veraltet. Aktualisieren Sie daher bitte den Bildschirm, um den neuesten Status anzuzeigen.", + "GroupNameAlreadyExistError": "(de)このTranscriptionistGroup名は既に登録されています。他のTranscriptionistGroup名で登録してください。", + "deleteFailedWorkflowAssigned": "(de)TranscriptionistGroupの削除に失敗しました。Workflow画面でルーティングルールから対象TranscriptionistGroupを外してください。", + "deleteFailedCheckoutPermissionExisted": "(de)TranscriptionistGroupの削除に失敗しました。Dictation画面でタスクのルーティングから対象TranscriptionistGroupを外してください。" } }, "worktypeIdSetting": { diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index 4c97388..e0f1800 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -424,7 +424,10 @@ }, "message": { "selectedTypistEmptyError": "One or more transcriptonist must be selected to save a transcrption group.", - "groupSaveFailedError": "Transcriptionist Group could not be saved. The displayed information may be outdated, so please refresh the screen to see the latest status." + "groupSaveFailedError": "Transcriptionist Group could not be saved. The displayed information may be outdated, so please refresh the screen to see the latest status.", + "GroupNameAlreadyExistError": "このTranscriptionistGroup名は既に登録されています。他のTranscriptionistGroup名で登録してください。", + "deleteFailedWorkflowAssigned": "TranscriptionistGroupの削除に失敗しました。Workflow画面でルーティングルールから対象TranscriptionistGroupを外してください。", + "deleteFailedCheckoutPermissionExisted": "TranscriptionistGroupの削除に失敗しました。Dictation画面でタスクのルーティングから対象TranscriptionistGroupを外してください。" } }, "worktypeIdSetting": { @@ -568,4 +571,4 @@ "close": "Close" } } -} \ No newline at end of file +} diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index aa43165..564958a 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -424,7 +424,10 @@ }, "message": { "selectedTypistEmptyError": "Se deben seleccionar uno o más transcriptores para guardar un grupo de transcripción.", - "groupSaveFailedError": "El grupo transcriptor no se pudo salvar. La información mostrada puede estar desactualizada. Así que actualice la pantalla para ver el estado más reciente." + "groupSaveFailedError": "El grupo transcriptor no se pudo salvar. La información mostrada puede estar desactualizada. Así que actualice la pantalla para ver el estado más reciente.", + "GroupNameAlreadyExistError": "(es)このTranscriptionistGroup名は既に登録されています。他のTranscriptionistGroup名で登録してください。", + "deleteFailedWorkflowAssigned": "(es)TranscriptionistGroupの削除に失敗しました。Workflow画面でルーティングルールから対象TranscriptionistGroupを外してください。", + "deleteFailedCheckoutPermissionExisted": "(es)TranscriptionistGroupの削除に失敗しました。Dictation画面でタスクのルーティングから対象TranscriptionistGroupを外してください。" } }, "worktypeIdSetting": { @@ -568,4 +571,4 @@ "close": "Cerrar" } } -} \ No newline at end of file +} diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index aafb91d..9cbdcd2 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -424,7 +424,10 @@ }, "message": { "selectedTypistEmptyError": "Un ou plusieurs transcripteurs doivent être sélectionnés pour enregistrer un groupe de transcription.", - "groupSaveFailedError": "Le groupe de transcriptionniste n'a pas pu être enregistré. Les informations affichées peuvent être obsolètes, veuillez donc actualiser l'écran pour voir le dernier statut." + "groupSaveFailedError": "Le groupe de transcriptionniste n'a pas pu être enregistré. Les informations affichées peuvent être obsolètes, veuillez donc actualiser l'écran pour voir le dernier statut.", + "GroupNameAlreadyExistError": "(fr)このTranscriptionistGroup名は既に登録されています。他のTranscriptionistGroup名で登録してください。", + "deleteFailedWorkflowAssigned": "(fr)TranscriptionistGroupの削除に失敗しました。Workflow画面でルーティングルールから対象TranscriptionistGroupを外してください。", + "deleteFailedCheckoutPermissionExisted": "(fr)TranscriptionistGroupの削除に失敗しました。Dictation画面でタスクのルーティングから対象TranscriptionistGroupを外してください。" } }, "worktypeIdSetting": { @@ -568,4 +571,4 @@ "close": "Fermer" } } -} \ No newline at end of file +} diff --git a/dictation_server/db/migrations/058-add_user_group_unique_constraint.sql b/dictation_server/db/migrations/058-add_user_group_unique_constraint.sql new file mode 100644 index 0000000..c9cb2d6 --- /dev/null +++ b/dictation_server/db/migrations/058-add_user_group_unique_constraint.sql @@ -0,0 +1,6 @@ +-- +migrate Up +ALTER TABLE `user_group` ADD UNIQUE `unique_index_account_id_name` (`account_id`, `name`); + + +-- +migrate Down +ALTER TABLE `user_group` DROP INDEX `unique_index_account_id_name`; diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index 8f9dc2f..3c488da 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -59,6 +59,7 @@ export const ErrorCodes = [ 'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合) 'E010812', // ライセンス未割当エラー 'E010908', // タイピストグループ不在エラー + 'E010909', // タイピストグループ名重複エラー 'E011001', // ワークタイプ重複エラー 'E011002', // ワークタイプ登録上限超過エラー 'E011003', // ワークタイプ不在エラー diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index 0a27cc5..9383694 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -48,6 +48,7 @@ export const errors: Errors = { E010811: 'Already license allocated Error', E010812: 'License not allocated Error', E010908: 'Typist Group not exist Error', + E010909: 'Typist Group name already exist Error', E011001: 'This WorkTypeID already used Error', E011002: 'WorkTypeID create limit exceeded Error', E011003: 'WorkTypeID not found Error', diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 50273e6..8b6e271 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -60,6 +60,7 @@ import { } from '../../repositories/licenses/errors/types'; import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service'; import { + TypistGroupNameAlreadyExistError, TypistGroupNotExistError, TypistIdInvalidError, } from '../../repositories/user_groups/errors/types'; @@ -1241,6 +1242,12 @@ export class AccountsService { makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST, ); + // 同名のタイピストグループが存在する場合は400エラーを返す + case TypistGroupNameAlreadyExistError: + throw new HttpException( + makeErrorResponse('E010909'), + HttpStatus.BAD_REQUEST, + ); default: throw new HttpException( makeErrorResponse('E009999'), @@ -1315,6 +1322,12 @@ export class AccountsService { makeErrorResponse('E010908'), HttpStatus.BAD_REQUEST, ); + // 同名のタイピストグループが存在する場合は400エラーを返す + case TypistGroupNameAlreadyExistError: + throw new HttpException( + makeErrorResponse('E010909'), + HttpStatus.BAD_REQUEST, + ); default: throw new HttpException( makeErrorResponse('E009999'), diff --git a/dictation_server/src/repositories/user_groups/errors/types.ts b/dictation_server/src/repositories/user_groups/errors/types.ts index 57aabbb..4a215a7 100644 --- a/dictation_server/src/repositories/user_groups/errors/types.ts +++ b/dictation_server/src/repositories/user_groups/errors/types.ts @@ -12,3 +12,10 @@ export class TypistIdInvalidError extends Error { this.name = 'TypistIdInvalidError'; } } +// 同名のタイピストグループが存在する場合のエラー +export class TypistGroupNameAlreadyExistError extends Error { + constructor(message: string) { + super(message); + this.name = 'TypistGroupNameAlreadyExistError'; + } +} \ No newline at end of file diff --git a/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts b/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts index 050d3e1..a900569 100644 --- a/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts +++ b/dictation_server/src/repositories/user_groups/user_groups.repository.service.ts @@ -1,9 +1,13 @@ import { Injectable } from '@nestjs/common'; -import { DataSource, In, IsNull } from 'typeorm'; +import { DataSource, In, IsNull, Not } from 'typeorm'; import { UserGroup } from './entity/user_group.entity'; import { UserGroupMember } from './entity/user_group_member.entity'; import { User } from '../users/entity/user.entity'; -import { TypistGroupNotExistError, TypistIdInvalidError } from './errors/types'; +import { + TypistGroupNameAlreadyExistError, + TypistGroupNotExistError, + TypistIdInvalidError, +} from './errors/types'; import { USER_ROLES } from '../../constants'; import { insertEntities, @@ -132,6 +136,19 @@ export class UserGroupsRepositoryService { )}`, ); } + // 同名のタイピストグループが存在するか確認する + const sameNameTypistGroup = await userGroupRepo.findOne({ + where: { + name, + account_id: accountId, + }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + }); + if (sameNameTypistGroup) { + throw new TypistGroupNameAlreadyExistError( + `TypistGroup already exists Error. accountId: ${accountId}; name: ${name}`, + ); + } // userGroupをDBに保存する const userGroup = await insertEntity( UserGroup, @@ -200,6 +217,21 @@ export class UserGroupsRepositoryService { ); } + // 同名のタイピストグループが存在するか確認する + const sameNameTypistGroup = await userGroupRepo.findOne({ + where: { + id: Not(typistGroupId), + name: typistGroupName, + account_id: accountId, + }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + }); + if (sameNameTypistGroup) { + throw new TypistGroupNameAlreadyExistError( + `TypistGroup already exists Error. accountId: ${accountId}; name: ${typistGroupName}`, + ); + } + // GroupIdが自アカウント内に存在するか確認する const typistGroup = await userGroupRepo.findOne({ where: {