Merged PR 713: 行ロック横展開3

## 概要
[Task3471: 行ロック横展開3](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3471)

- 以下のリポジトリのメソッドに行ロックを追加しました。
- tasks
  - checkout
    - タスク、チェックアウト情報にロックを追加して途中で変更できないようにする
  - checkin
    - タスクのチェックにロックを追加してタスクのステータスを途中で変更できないようにする
  - cancel
    - タスクのチェックにロックを追加してタスクのステータスを途中で変更できないようにする
  - suspend
    - タスクのチェックにロックを追加してタスクのステータスを途中で変更できないようにする
  - backup
    - タスクのチェックにロックを追加してタスクのステータスを途中で変更できないようにする
  - create
    - タスクのチェックにロックを追加してJobNumberのチェックで重複しないようにする
  - changeCheckoutPermission
    - タスク、チェックアウト候補のチェックにロックを追加して途中で変更されないようにする
  - autoRouting
    - 処理中にワークフロー・ワークタイプの取得にロックを追加して意図しない対象のでチェックアウト権限が作成されないようにする
    - ワークタイプ・ワークフローの更新/削除時にもロックを追加

こちらの資料を参考に対応しています。
[行ロックに関する影響調査](https://ndstokyo.sharepoint.com//r/sites/Piranha/Shared%20Documents/General/OMDS/%E8%A1%8C%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E5%BD%B1%E9%9F%BF%E8%AA%BF%E6%9F%BB.xlsx?d=wdd6f3d97f7b04a538095c459f8eee2eb&csf=1&web=1&e=9M43di)

対応箇所について以下にまとめました。
[Task3471](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/Task3471?csf=1&web=1&e=wptJqD)

## レビューポイント
- 対応箇所は適切でしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2024-02-05 06:19:24 +00:00
parent 1daeedbfdb
commit 878657ad4a
3 changed files with 94 additions and 37 deletions

View File

@ -0,0 +1,27 @@
-- +migrate Up
ALTER TABLE `tasks` ADD INDEX `idx_tasks_audio_file_id` (audio_file_id);
ALTER TABLE `tasks` ADD INDEX `idx_tasks_status` (status);
ALTER TABLE `tasks` ADD INDEX `idx_tasks_typist_user_id` (typist_user_id);
ALTER TABLE `tasks` ADD INDEX `idx_tasks_is_job_number_enabled` (is_job_number_enabled);
ALTER TABLE `checkout_permission` ADD INDEX `idx_checkout_permission_task_id` (task_id);
ALTER TABLE `checkout_permission` ADD INDEX `idx_checkout_permission_user_group_id` (user_group_id);
ALTER TABLE `checkout_permission` ADD INDEX `idx_checkout_permission_user_id` (user_id);
ALTER TABLE `users` ADD INDEX `idx_users_role` (role);
ALTER TABLE `users` ADD INDEX `idx_users_author_id` (author_id);
ALTER TABLE `users` ADD INDEX `idx_users_deleted_at` (deleted_at);
ALTER TABLE `worktypes` ADD INDEX `idx_worktypes_custom_worktype_id` (custom_worktype_id);
ALTER TABLE `workflows` ADD INDEX `idx_workflows_account_id` (account_id);
-- +migrate Down
ALTER TABLE `tasks` DROP INDEX `idx_tasks_audio_file_id`;
ALTER TABLE `tasks` DROP INDEX `idx_tasks_status`;
ALTER TABLE `tasks` DROP INDEX `idx_tasks_typist_user_id`;
ALTER TABLE `tasks` DROP INDEX `idx_tasks_is_job_number_enabled`;
ALTER TABLE `checkout_permission` DROP INDEX `idx_checkout_permission_task_id`;
ALTER TABLE `checkout_permission` DROP INDEX `idx_checkout_permission_user_group_id`;
ALTER TABLE `checkout_permission` DROP INDEX `idx_checkout_permission_user_id`;
ALTER TABLE `users` DROP INDEX `idx_users_role`;
ALTER TABLE `users` DROP INDEX `idx_users_author_id`;
ALTER TABLE `users` DROP INDEX `idx_users_deleted_at`;
ALTER TABLE `worktypes` DROP INDEX `idx_worktypes_custom_worktype_id`;
ALTER TABLE `workflows` DROP INDEX `idx_workflows_account_id`;

View File

@ -189,6 +189,7 @@ export class TasksRepositoryService {
audio_file_id: audio_file_id,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (!task) {
throw new TasksNotFoundError(
@ -204,6 +205,7 @@ export class TasksRepositoryService {
typist_user_id: user_id,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (tasks.length > 0) {
@ -242,6 +244,7 @@ export class TasksRepositoryService {
},
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// ユーザーの所属するすべてのグループIDを列挙
const groupIds = groups.map((member) => member.user_group_id);
@ -264,6 +267,7 @@ export class TasksRepositoryService {
},
],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
//チェックアウト権限がなければエラー
@ -334,6 +338,7 @@ export class TasksRepositoryService {
audio_file_id: audio_file_id,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (!task) {
throw new TasksNotFoundError(
@ -387,6 +392,7 @@ export class TasksRepositoryService {
audio_file_id: audio_file_id,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (!task) {
throw new TasksNotFoundError(
@ -462,6 +468,7 @@ export class TasksRepositoryService {
audio_file_id: audio_file_id,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (!task) {
throw new TasksNotFoundError(
@ -513,6 +520,7 @@ export class TasksRepositoryService {
audio_file_id: audio_file_id,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (!task) {
throw new TasksNotFoundError(
@ -896,6 +904,7 @@ export class TasksRepositoryService {
where: { account_id: account_id, is_job_number_enabled: true },
order: { created_at: 'DESC', job_number: 'DESC' },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
let newJobNumber = '00000001';
@ -958,30 +967,6 @@ export class TasksRepositoryService {
assignees: Assignee[],
): Promise<void> {
await this.dataSource.transaction(async (entityManager) => {
// UserGroupの取得/存在確認
const userGroupIds = assignees
.filter((x) => x.typistGroupId !== undefined)
.map((y) => {
return y.typistGroupId;
});
const groupRepo = entityManager.getRepository(UserGroup);
const groupRecords = await groupRepo.find({
where: {
id: In(userGroupIds),
account_id: account_id,
deleted_at: IsNull(),
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
// idはユニークであるため取得件数の一致でグループの存在を確認
if (userGroupIds.length !== groupRecords.length) {
throw new TypistUserGroupNotFoundError(
`Group not exists Error. reqUserGroupId:${userGroupIds}; resUserGroupId:${groupRecords.map(
(x) => x.id,
)}`,
);
}
// Userの取得/存在確認
const typistUserIds = assignees
.filter((x) => x.typistUserId !== undefined)
@ -1009,6 +994,31 @@ export class TasksRepositoryService {
);
}
// UserGroupの取得/存在確認
const userGroupIds = assignees
.filter((x) => x.typistGroupId !== undefined)
.map((y) => {
return y.typistGroupId;
});
const groupRepo = entityManager.getRepository(UserGroup);
const groupRecords = await groupRepo.find({
where: {
id: In(userGroupIds),
account_id: account_id,
deleted_at: IsNull(),
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// idはユニークであるため取得件数の一致でグループの存在を確認
if (userGroupIds.length !== groupRecords.length) {
throw new TypistUserGroupNotFoundError(
`Group not exists Error. reqUserGroupId:${userGroupIds}; resUserGroupId:${groupRecords.map(
(x) => x.id,
)}`,
);
}
// 引数audioFileIdを使ってTaskレコードを特定し、そのステータスを取得/存在確認
const taskRepo = entityManager.getRepository(Task);
@ -1024,6 +1034,7 @@ export class TasksRepositoryService {
},
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
//タスクが存在しない or ステータスがUploadedでなければエラー
if (!taskRecord) {
@ -1187,39 +1198,51 @@ export class TasksRepositoryService {
return await this.dataSource.transaction(async (entityManager) => {
// 音声ファイルを取得
const audioFileRepo = entityManager.getRepository(AudioFile);
const audioFile = await audioFileRepo.findOne({
relations: {
task: true,
},
const audio = await audioFileRepo.findOne({
where: {
id: audioFileId,
account_id: accountId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
if (!audioFile) {
if (!audio) {
throw new Error(
`audio file not found. audio_file_id:${audioFileId}, accountId:${accountId}`,
);
}
const { task } = audioFile;
if (!task) {
throw new Error(
`task not found. audio_file_id:${audioFileId}, accountId:${accountId}`,
);
}
// authorIdをもとにユーザーを取得
const userRepo = entityManager.getRepository(User);
const authorUser = await userRepo.findOne({
where: {
author_id: audioFile.author_id,
author_id: audio.author_id,
account_id: accountId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// TaskとFileを取得
const taskRepo = entityManager.getRepository(Task);
const task = await taskRepo.findOne({
relations: {
file: true,
},
where: {
account_id: accountId,
audio_file_id: audioFileId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
const audioFile = task?.file;
if (!audioFile) {
throw new Error(
`audio file not found. audio_file_id:${audioFileId}, accountId:${accountId}`,
);
}
// 音声ファイル上のworktypeIdをもとにworktypeを取得
const worktypeRepo = entityManager.getRepository(Worktype);
const worktypeRecord = await worktypeRepo.findOne({
@ -1228,6 +1251,7 @@ export class TasksRepositoryService {
account_id: accountId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// 音声ファイル上のworktypeIdが設定されているが、一致するworktypeが存在しない場合はエラーを出して終了
@ -1249,6 +1273,7 @@ export class TasksRepositoryService {
worktype_id: worktypeRecord?.id ?? IsNull(),
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// Workflowルーティングルールがあればタスクのチェックアウト権限を設定する
@ -1276,6 +1301,7 @@ export class TasksRepositoryService {
account_id: accountId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (!myAuthorUser) {
throw new Error(
@ -1292,6 +1318,7 @@ export class TasksRepositoryService {
worktype_id: worktypeRecord?.id ?? IsNull(),
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// API実行者のAuthorIdと音声ファイルのWorktypeをもとにルーティングルールを取得できない場合はエラーを出して終了
@ -1358,6 +1385,7 @@ export class TasksRepositoryService {
const typistUsers = await userRepo.find({
where: { account_id: accountId, id: In(typistIds) },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (typistUsers.length !== typistIds.length) {
throw new Error(`typist not found. ids: ${typistIds}`);
@ -1371,6 +1399,7 @@ export class TasksRepositoryService {
const typistGroups = await userGroupRepo.find({
where: { account_id: accountId, id: In(groupIds) },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
if (typistGroups.length !== groupIds.length) {
throw new Error(`typist group not found. ids: ${groupIds}`);

View File

@ -220,6 +220,7 @@ export class WorktypesRepositoryService {
const worktype = await worktypeRepo.findOne({
where: { account_id: accountId, id: id },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
lock: { mode: 'pessimistic_write' },
});
// ワークタイプが存在しない場合はエラー
if (!worktype) {