diff --git a/dictation_server/src/features/tasks/tasks.controller.ts b/dictation_server/src/features/tasks/tasks.controller.ts index baa338a..364142c 100644 --- a/dictation_server/src/features/tasks/tasks.controller.ts +++ b/dictation_server/src/features/tasks/tasks.controller.ts @@ -87,7 +87,7 @@ export class TasksController { ? body.direction : undefined; - const { tasks, total } = await this.taskService.getTasksFromAccountId( + const { tasks, total } = await this.taskService.getTasks( decodedToken, offset, limit, diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index 07d44c1..649034b 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -22,7 +22,7 @@ describe('TasksService', () => { const paramName = 'JOB_NUMBER'; const direction = 'ASC'; expect( - await service.tasksService.getTasksFromAccountId( + await service.tasksService.getTasks( accessToken, offset, limit, @@ -87,7 +87,7 @@ describe('TasksService', () => { const paramName = 'JOB_NUMBER'; const direction = 'ASC'; await expect( - service.tasksService.getTasksFromAccountId( + service.tasksService.getTasks( accessToken, offset, limit, @@ -119,7 +119,7 @@ describe('TasksService', () => { const paramName = 'JOB_NUMBER'; const direction = 'ASC'; await expect( - service.tasksService.getTasksFromAccountId( + service.tasksService.getTasks( accessToken, offset, limit, @@ -148,7 +148,7 @@ describe('TasksService', () => { status: 'Uploaded', priority: '00', created_at: new Date('2023-01-01T01:01:01.000'), - option_items: undefined, + option_items: undefined, // 存在しない場合でも空配列であるはずのものがundefined file: { id: 1, account_id: 1, @@ -185,7 +185,7 @@ describe('TasksService', () => { const paramName = 'JOB_NUMBER'; const direction = 'ASC'; await expect( - service.tasksService.getTasksFromAccountId( + service.tasksService.getTasks( accessToken, offset, limit, @@ -220,7 +220,7 @@ describe('TasksService', () => { const status = ['Uploaded,Backup']; const paramName = 'JOB_NUMBER'; const direction = 'ASC'; - const result = await service.tasksService.getTasksFromAccountId( + const result = await service.tasksService.getTasks( accessToken, offset, limit, @@ -292,7 +292,7 @@ describe('TasksService', () => { const paramName = 'JOB_NUMBER'; const direction = 'ASC'; await expect( - service.tasksService.getTasksFromAccountId( + service.tasksService.getTasks( accessToken, offset, limit, @@ -323,7 +323,7 @@ describe('TasksService', () => { const paramName = 'JOB_NUMBER'; const direction = 'ASC'; await expect( - service.tasksService.getTasksFromAccountId( + service.tasksService.getTasks( accessToken, offset, limit, diff --git a/dictation_server/src/features/tasks/tasks.service.ts b/dictation_server/src/features/tasks/tasks.service.ts index 2aadc77..678633e 100644 --- a/dictation_server/src/features/tasks/tasks.service.ts +++ b/dictation_server/src/features/tasks/tasks.service.ts @@ -20,7 +20,7 @@ export class TasksService { ) {} // TODO: 引数にAccessTokenがあるのは不適切なのでController側で分解したい - async getTasksFromAccountId( + async getTasks( accessToken: AccessToken, offset: number, limit: number, @@ -40,6 +40,9 @@ export class TasksService { await this.usersRepository.findUserByExternalId(userId); if (roles.includes(ADMIN_ROLES.ADMIN)) { + const { account_id } = await this.usersRepository.findUserByExternalId( + userId, + ); const result = await this.taskRepository.getTasksFromAccountId( account_id, offset, @@ -69,7 +72,18 @@ export class TasksService { } if (roles.includes(USER_ROLES.TYPIST)) { - throw new Error(`NOT IMPLEMENTED`); + const result = await this.taskRepository.getTasksFromTypistRelations( + userId, + offset, + limit, + paramName ?? defaultParamName, + direction ?? defaultDirection, + status, + ); + + const tasks = createTasks(result.tasks, result.permissions); + + return { tasks: tasks, total: result.count }; } throw new Error(`invalid roles: ${roles.join(',')}`); diff --git a/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts b/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts index c4587e4..fb03060 100644 --- a/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts +++ b/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts @@ -1,3 +1,4 @@ +import { Task } from '../../../repositories/tasks/entity/task.entity'; import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity'; import { User } from '../../../repositories/users/entity/user.entity'; import { @@ -6,6 +7,7 @@ import { PrimaryGeneratedColumn, JoinColumn, OneToOne, + ManyToOne, } from 'typeorm'; @Entity({ name: 'checkout_permission' }) @@ -29,4 +31,8 @@ export class CheckoutPermission { @OneToOne(() => UserGroup, (group) => group.id) @JoinColumn({ name: 'user_group_id' }) user_group?: UserGroup; + + @ManyToOne(() => Task, (task) => task.id) + @JoinColumn({ name: 'task_id' }) + task?: Task; } diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index 30f0d3f..a2d2541 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -15,6 +15,7 @@ import { SortDirection, TaskListSortableAttribute, } from '../../common/types/sort'; +import { UserGroupMember } from '../user_groups/entity/user_group_member.entity'; @Injectable() export class TasksRepositoryService { @@ -155,6 +156,127 @@ export class TasksRepositoryService { return value; } + async getTasksFromTypistRelations( + external_user_id: string, + offset: number, + limit: number, + sort_criteria: TaskListSortableAttribute, + direction: SortDirection, + status: string[], + ): Promise<{ + tasks: Task[]; + permissions: CheckoutPermission[]; + count: number; + }> { + const order = makeOrder(sort_criteria, direction); + + const value = await this.dataSource.transaction(async (entityManager) => { + const groupMemberRepo = entityManager.getRepository(UserGroupMember); + // ユーザーの所属するすべてのグループを列挙 + const groups = await groupMemberRepo.find({ + relations: { + user: true, + }, + where: { + user: { + external_id: external_user_id, + }, + }, + }); + // ユーザーの所属するすべてのグループIDを列挙 + const groupIds = groups.map((member) => member.user_group_id); + + const checkoutRepo = entityManager.getRepository(CheckoutPermission); + // ユーザーに対するチェックアウト権限、またはユーザーの所属するユーザーグループのチェックアウト権限を取得 + const related = await checkoutRepo.find({ + relations: { + task: true, + }, + where: [ + { + // ユーザーがチェックアウト可能である + user: { + external_id: external_user_id, + }, + }, + { + // ユーザーの所属するユーザーグループがチェックアウト可能である + user_group_id: In(groupIds), + }, + ], + }); + + // ユーザー本人、またはユーザーが所属するユーザーグループがチェックアウト可能なタスクIDの一覧を作成 + const relatedTaskIds = related.map((permission) => permission.task_id); + + const taskRepo = entityManager.getRepository(Task); + + // [✔] 自分が割り当てられている + // [✔] または 自分が割り当て可能になっている + // [✔] または 自分が所属しているTypistGroupが割り当て可能になっている + // という条件のTask一覧を取得する + + // limit/offsetによらず条件に一致するすべてのレコード数を取得 + const count = await taskRepo.count({ + where: [ + { + // Typistが割り当てられているTaskを取得 + typist_user: { + external_id: external_user_id, + }, + status: In(status), + }, + { + // TypistまたはTypistが所属するユーザーグループが割り当て可能になっているTaskを取得 + id: In(relatedTaskIds), + status: In(status), + }, + ], + }); + + // 条件に該当するTask一覧を取得 + const tasks = await taskRepo.find({ + relations: { + file: true, + option_items: true, + typist_user: true, + }, + where: [ + { + // Typistが割り当てられているTaskを取得 + typist_user: { + external_id: external_user_id, + }, + status: In(status), + }, + { + // TypistまたはTypistが所属するユーザーグループが割り当て可能になっているTaskを取得 + id: In(relatedTaskIds), + status: In(status), + }, + ], + order: order, // 引数によってOrderに使用するパラメータを変更 + take: limit, + skip: offset, + }); + + // TODO: Task内にCheckoutPermissionを含める方法が上手くいかなかった(複雑になりすぎた? 原因未調査)ため、 + // 確実に上手くいく方法としてQueryの分割を行ったが、本来はオブジェクトの構築はTypeORMに一任したい + const taskIds = tasks.map((x) => x.id); + const permissions = await checkoutRepo.find({ + relations: { + user: true, + user_group: true, + }, + where: { + task_id: In(taskIds), + }, + }); + return { tasks, permissions, count }; + }); + return value; + } + /** * 文字起こしタスクと音声ファイル、オプションアイテムを追加 */ diff --git a/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts b/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts new file mode 100644 index 0000000..15aba70 --- /dev/null +++ b/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts @@ -0,0 +1,39 @@ +import { User } from '../../../repositories/users/entity/user.entity'; +import { + Entity, + Column, + PrimaryGeneratedColumn, + OneToOne, + JoinColumn, +} from 'typeorm'; + +@Entity({ name: 'user_group_member' }) +export class UserGroupMember { + @PrimaryGeneratedColumn() + id: number; + + @Column() + user_group_id: number; + + @Column() + user_id: string; + + @Column({ nullable: true }) + deleted_at?: Date; + + @Column() + created_by: string; + + @Column({ nullable: true }) + created_at?: Date; + + @Column() + updated_by: string; + + @Column({ nullable: true }) + updated_at?: Date; + + @OneToOne(() => User, (user) => user.id) + @JoinColumn({ name: 'user_id' }) + user?: User; +} diff --git a/dictation_server/src/repositories/user_groups/user_groups.repository.module.ts b/dictation_server/src/repositories/user_groups/user_groups.repository.module.ts index 8f194c4..0d9429e 100644 --- a/dictation_server/src/repositories/user_groups/user_groups.repository.module.ts +++ b/dictation_server/src/repositories/user_groups/user_groups.repository.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserGroupsRepositoryService } from './user_groups.repository.service'; import { UserGroup } from './entity/user_group.entity'; +import { UserGroupMember } from './entity/user_group_member.entity'; @Module({ - imports: [TypeOrmModule.forFeature([UserGroup])], + imports: [TypeOrmModule.forFeature([UserGroup, UserGroupMember])], providers: [UserGroupsRepositoryService], exports: [UserGroupsRepositoryService], })