Merged PR 513: 次タスク取得API実装
## 概要 [Task2874: 次タスク取得API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2874) - 次タスク取得APIとテストを実装しました。 ## レビューポイント - リポジトリからのタスク取得ロジックは適切か - テストケースは適切か ## UIの変更 - なし ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
f3dde1874d
commit
b314fe4b46
@ -14,7 +14,6 @@ import { UserGroup } from '../../../repositories/user_groups/entity/user_group.e
|
||||
import { UserGroupsRepositoryService } from '../../../repositories/user_groups/user_groups.repository.service';
|
||||
import { AdB2cUser } from '../../../gateways/adb2c/types/types';
|
||||
import { LicensesRepositoryService } from '../../../repositories/licenses/licenses.repository.service';
|
||||
import { Context } from '../../../common/log';
|
||||
import { BlobstorageService } from '../../../gateways/blobstorage/blobstorage.service';
|
||||
import { Worktype } from '../../../repositories/worktypes/entity/worktype.entity';
|
||||
import { WorktypesRepositoryService } from '../../../repositories/worktypes/worktypes.repository.service';
|
||||
|
||||
@ -153,11 +153,41 @@ export class TasksController {
|
||||
'指定した文字起こしタスクの次のタスクに紐づく音声ファイルIDを取得します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({
|
||||
roles: [USER_ROLES.TYPIST],
|
||||
}),
|
||||
)
|
||||
async getNextAudioFile(
|
||||
@Headers() headers,
|
||||
@Query() body: AudioNextRequest,
|
||||
@Req() req: Request,
|
||||
@Query() param: AudioNextRequest,
|
||||
): Promise<AudioNextResponse> {
|
||||
return { nextFileId: 1234 };
|
||||
const { endedFileId } = param;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
|
||||
const nextFileId = await this.taskService.getNextTask(
|
||||
context,
|
||||
userId,
|
||||
endedFileId,
|
||||
);
|
||||
|
||||
return { nextFileId };
|
||||
}
|
||||
|
||||
@Post(':audioFileId/checkout')
|
||||
|
||||
@ -20,8 +20,14 @@ import {
|
||||
} from './test/utility';
|
||||
import { Adb2cTooManyRequestsError } from '../../gateways/adb2c/adb2c.service';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeTestSimpleAccount, makeTestUser } from '../../common/test/utility';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import {
|
||||
makeTestAccount,
|
||||
makeTestSimpleAccount,
|
||||
makeTestUser,
|
||||
} from '../../common/test/utility';
|
||||
import { ADMIN_ROLES, TASK_STATUS, USER_ROLES } from '../../constants';
|
||||
import { makeTestingModule } from '../../common/test/modules';
|
||||
import { createSortCriteria } from '../users/test/utility';
|
||||
|
||||
describe('TasksService', () => {
|
||||
it('タスク一覧を取得できる(admin)', async () => {
|
||||
@ -2460,3 +2466,480 @@ describe('cancel', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNextTask', () => {
|
||||
let source: DataSource | null = 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 () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('次タスクを取得できる(JobNumber順)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
author_id: 'AUTHOR_ID',
|
||||
external_id: 'author-user-external-id',
|
||||
role: USER_ROLES.AUTHOR,
|
||||
});
|
||||
const { id: typistUserId, external_id: typistExternalId } =
|
||||
await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
|
||||
await createSortCriteria(source, typistUserId, 'JOB_NUMBER', 'ASC');
|
||||
|
||||
const { taskId: taskId1 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId1, typistUserId);
|
||||
|
||||
const { taskId: taskId3, audioFileId: audioFileId3 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000003',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId3, typistUserId);
|
||||
|
||||
const { taskId: taskId2, audioFileId: audioFileId2 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000002',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId2, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const nextAudioFileId = await service.getNextTask(
|
||||
context,
|
||||
typistExternalId,
|
||||
audioFileId2,
|
||||
);
|
||||
|
||||
// 実行結果が正しいか確認
|
||||
{
|
||||
expect(nextAudioFileId).toEqual(audioFileId3);
|
||||
}
|
||||
});
|
||||
|
||||
it('次タスクを取得できる(JobNumber順+優先度)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
author_id: 'AUTHOR_ID',
|
||||
external_id: 'author-user-external-id',
|
||||
role: USER_ROLES.AUTHOR,
|
||||
});
|
||||
const { id: typistUserId, external_id: typistExternalId } =
|
||||
await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
|
||||
await createSortCriteria(source, typistUserId, 'JOB_NUMBER', 'ASC');
|
||||
|
||||
const { taskId: taskId1, audioFileId: audioFileId1 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'00',
|
||||
'00000001',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId1, typistUserId);
|
||||
|
||||
const { taskId: taskId3, audioFileId: audioFileId3 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'00',
|
||||
'00000003',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId3, typistUserId);
|
||||
|
||||
const { taskId: taskId2 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000002',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId2, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const nextAudioFileId = await service.getNextTask(
|
||||
context,
|
||||
typistExternalId,
|
||||
audioFileId1,
|
||||
);
|
||||
|
||||
// 実行結果が正しいか確認
|
||||
{
|
||||
expect(nextAudioFileId).toEqual(audioFileId3);
|
||||
}
|
||||
});
|
||||
|
||||
it('次タスクを取得できる(JobNumber順、先頭)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
author_id: 'AUTHOR_ID',
|
||||
external_id: 'author-user-external-id',
|
||||
role: USER_ROLES.AUTHOR,
|
||||
});
|
||||
const { id: typistUserId, external_id: typistExternalId } =
|
||||
await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
|
||||
await createSortCriteria(source, typistUserId, 'JOB_NUMBER', 'ASC');
|
||||
|
||||
const { taskId: taskId1, audioFileId: audioFileId1 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId1, typistUserId);
|
||||
|
||||
const { taskId: taskId3, audioFileId: audioFileId3 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000003',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId3, typistUserId);
|
||||
|
||||
const { taskId: taskId2 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000002',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId2, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const nextAudioFileId = await service.getNextTask(
|
||||
context,
|
||||
typistExternalId,
|
||||
audioFileId3,
|
||||
);
|
||||
|
||||
// 実行結果が正しいか確認
|
||||
{
|
||||
expect(nextAudioFileId).toEqual(audioFileId1);
|
||||
}
|
||||
});
|
||||
|
||||
it('次タスクを取得できる(Worktype順)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
author_id: 'AUTHOR_ID',
|
||||
external_id: 'author-user-external-id',
|
||||
role: USER_ROLES.AUTHOR,
|
||||
});
|
||||
const { id: typistUserId, external_id: typistExternalId } =
|
||||
await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
|
||||
await createSortCriteria(source, typistUserId, 'WORK_TYPE', 'ASC');
|
||||
|
||||
const { taskId: taskId1 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'worktype1',
|
||||
'01',
|
||||
'00000001',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId1, typistUserId);
|
||||
|
||||
const { taskId: taskId3, audioFileId: audioFileId3 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'worktype2',
|
||||
'01',
|
||||
'00000003',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId3, typistUserId);
|
||||
|
||||
const { taskId: taskId2, audioFileId: audioFileId2 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'worktype3',
|
||||
'01',
|
||||
'00000002',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId2, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const nextAudioFileId = await service.getNextTask(
|
||||
context,
|
||||
typistExternalId,
|
||||
audioFileId3,
|
||||
);
|
||||
|
||||
// 実行結果が正しいか確認
|
||||
{
|
||||
expect(nextAudioFileId).toEqual(audioFileId2);
|
||||
}
|
||||
});
|
||||
|
||||
it('次タスクを取得できる(Status順)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
author_id: 'AUTHOR_ID',
|
||||
external_id: 'author-user-external-id',
|
||||
role: USER_ROLES.AUTHOR,
|
||||
});
|
||||
const { id: typistUserId, external_id: typistExternalId } =
|
||||
await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
|
||||
await createSortCriteria(source, typistUserId, 'STATUS', 'ASC');
|
||||
|
||||
const { taskId: taskId1, audioFileId: audioFileId1 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'worktype1',
|
||||
'01',
|
||||
'00000001',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId1, typistUserId);
|
||||
|
||||
const { taskId: taskId3 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'worktype2',
|
||||
'01',
|
||||
'00000003',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId3, typistUserId);
|
||||
|
||||
const { taskId: taskId2, audioFileId: audioFileId2 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'worktype3',
|
||||
'01',
|
||||
'00000002',
|
||||
TASK_STATUS.PENDING,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId2, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const nextAudioFileId = await service.getNextTask(
|
||||
context,
|
||||
typistExternalId,
|
||||
audioFileId2,
|
||||
);
|
||||
|
||||
// 実行結果が正しいか確認
|
||||
{
|
||||
expect(nextAudioFileId).toEqual(audioFileId1);
|
||||
}
|
||||
});
|
||||
|
||||
it('次タスクが存在しない場合undefinedを返す(JobNumber順)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
author_id: 'AUTHOR_ID',
|
||||
external_id: 'author-user-external-id',
|
||||
role: USER_ROLES.AUTHOR,
|
||||
});
|
||||
const { id: typistUserId, external_id: typistExternalId } =
|
||||
await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
|
||||
await createSortCriteria(source, typistUserId, 'JOB_NUMBER', 'ASC');
|
||||
|
||||
const { taskId: taskId1, audioFileId: audioFileId1 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'worktype1',
|
||||
'01',
|
||||
'00000001',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId1, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
const nextAudioFileId = await service.getNextTask(
|
||||
context,
|
||||
typistExternalId,
|
||||
audioFileId1,
|
||||
);
|
||||
|
||||
// 実行結果が正しいか確認
|
||||
{
|
||||
expect(nextAudioFileId).toEqual(undefined);
|
||||
}
|
||||
});
|
||||
it('指定タスクが存在しない場合エラーを返す(JobNumber順)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
author_id: 'AUTHOR_ID',
|
||||
external_id: 'author-user-external-id',
|
||||
role: USER_ROLES.AUTHOR,
|
||||
});
|
||||
const { id: typistUserId, external_id: typistExternalId } =
|
||||
await makeTestUser(source, {
|
||||
account_id: account.id,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: USER_ROLES.TYPIST,
|
||||
});
|
||||
|
||||
await createSortCriteria(source, typistUserId, 'WORK_TYPE', 'ASC');
|
||||
|
||||
const { taskId: taskId1, audioFileId: audioFileId1 } = await createTask(
|
||||
source,
|
||||
account.id,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'worktype1',
|
||||
'01',
|
||||
'00000001',
|
||||
TASK_STATUS.UPLOADED,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId1, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
// 実行結果が正しいか確認
|
||||
try {
|
||||
await service.getNextTask(context, typistExternalId, audioFileId1 + 1);
|
||||
fail();
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -157,6 +157,81 @@ export class TasksService {
|
||||
this.logger.log(`[OUT] [${context.trackingId}] ${this.getTasks.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 完了したタスクの次のタスクを取得します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param fileId
|
||||
* @returns next task
|
||||
*/
|
||||
async getNextTask(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
fileId: number,
|
||||
): Promise<number | undefined> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.getNextTask.name} | params: { externalId: ${externalId}, fileId: ${fileId} };`,
|
||||
);
|
||||
|
||||
try {
|
||||
const { account_id: accountId, id } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
// タスク一覧を取得する
|
||||
const tasks = await this.taskRepository.getSortedTasks(
|
||||
accountId,
|
||||
id,
|
||||
fileId,
|
||||
);
|
||||
|
||||
// 指定タスクのインデックスを取得する
|
||||
const targetTaskIndex = tasks.findIndex((x) => x.audio_file_id == fileId);
|
||||
|
||||
// 指定したタスクが見つからない場合はエラーとする(リポジトリからは必ず取得できる想定)
|
||||
if (targetTaskIndex === -1) {
|
||||
throw new TasksNotFoundError(`task not found: ${fileId}`);
|
||||
}
|
||||
|
||||
// ソート順に並んだタスクについて、指定した完了済みタスクの次のタスクを取得する
|
||||
let nextTaskIndex = targetTaskIndex + 1;
|
||||
|
||||
// 次のタスクがない場合は先頭のタスクを返す
|
||||
if (tasks.length - 1 < nextTaskIndex) {
|
||||
nextTaskIndex = 0;
|
||||
}
|
||||
|
||||
const nextTask = tasks[nextTaskIndex];
|
||||
|
||||
// 先頭のタスクが指定した完了済みタスクの場合は次のタスクがないためundefinedを返す
|
||||
return nextTask.audio_file_id === fileId
|
||||
? undefined
|
||||
: nextTask.audio_file_id;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case TasksNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010603'),
|
||||
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.getNextTask.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定した音声ファイルに紐づくタスクをcheckoutする
|
||||
* @param audioFileId
|
||||
|
||||
@ -110,7 +110,7 @@ export const createTask = async (
|
||||
jobNumber: string,
|
||||
status: string,
|
||||
typist_user_id?: number | undefined,
|
||||
): Promise<{ taskId: number }> => {
|
||||
): Promise<{ taskId: number; audioFileId: number }> => {
|
||||
const { identifiers: audioFileIdentifiers } = await datasource
|
||||
.getRepository(AudioFile)
|
||||
.insert({
|
||||
@ -144,7 +144,7 @@ export const createTask = async (
|
||||
created_at: new Date(),
|
||||
});
|
||||
const task = taskIdentifiers.pop() as Task;
|
||||
return { taskId: task.id };
|
||||
return { taskId: task.id, audioFileId: audioFile.id };
|
||||
};
|
||||
/**
|
||||
*
|
||||
@ -162,8 +162,8 @@ export const createCheckoutPermissions = async (
|
||||
): Promise<void> => {
|
||||
await datasource.getRepository(CheckoutPermission).insert({
|
||||
task_id: task_id,
|
||||
user_id: user_id,
|
||||
user_group_id: user_group_id,
|
||||
user_id: user_id ?? null,
|
||||
user_group_id: user_group_id ?? null,
|
||||
});
|
||||
};
|
||||
/**
|
||||
|
||||
@ -35,6 +35,7 @@ import { License } from '../../../repositories/licenses/entity/license.entity';
|
||||
import { AdB2cMockValue, makeAdB2cServiceMock } from './users.service.mock';
|
||||
import { AdB2cService } from '../../../gateways/adb2c/adb2c.service';
|
||||
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../../constants';
|
||||
import { SortCriteria } from '../../../repositories/sort_criteria/entity/sort_criteria.entity';
|
||||
|
||||
export const getLicenses = async (
|
||||
datasource: DataSource,
|
||||
@ -164,3 +165,16 @@ export const makeTestingModuleWithAdb2c = async (
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
export const createSortCriteria = async (
|
||||
datasource: DataSource,
|
||||
userId: number,
|
||||
parameter: string,
|
||||
direction: string,
|
||||
): Promise<void> => {
|
||||
await datasource.getRepository(SortCriteria).insert({
|
||||
user_id: userId,
|
||||
parameter: parameter,
|
||||
direction: direction,
|
||||
});
|
||||
};
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
ManyToOne,
|
||||
} from 'typeorm';
|
||||
|
||||
@ -25,11 +24,11 @@ export class CheckoutPermission {
|
||||
@Column({ nullable: true, type: 'bigint', transformer: bigintTransformer })
|
||||
user_group_id: number | null;
|
||||
|
||||
@OneToOne(() => User, (user) => user.id)
|
||||
@ManyToOne(() => User, (user) => user.id)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user: User | null;
|
||||
|
||||
@OneToOne(() => UserGroup, (group) => group.id)
|
||||
@ManyToOne(() => UserGroup, (group) => group.id)
|
||||
@JoinColumn({ name: 'user_group_id' })
|
||||
user_group: UserGroup | null;
|
||||
|
||||
|
||||
@ -15,6 +15,8 @@ import { CheckoutPermission } from '../checkout_permissions/entity/checkout_perm
|
||||
import {
|
||||
SortDirection,
|
||||
TaskListSortableAttribute,
|
||||
isSortDirection,
|
||||
isTaskListSortableAttribute,
|
||||
} from '../../common/types/sort';
|
||||
import { UserGroupMember } from '../user_groups/entity/user_group_member.entity';
|
||||
import { Assignee } from '../../features/tasks/types/types';
|
||||
@ -32,6 +34,7 @@ import {
|
||||
} from './errors/types';
|
||||
import { Roles } from '../../common/types/role';
|
||||
import { TaskStatus, isTaskStatus } from '../../common/types/taskStatus';
|
||||
import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TasksRepositoryService {
|
||||
@ -853,6 +856,102 @@ export class TasksRepositoryService {
|
||||
return await checkoutPermissionRepo.save(checkoutPermissions);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 対象ユーザーのソート順でソートしたタスク一覧を取得します(指定タスクとユーザが着手可能なタスクの一覧を取得します)
|
||||
* @param accountId
|
||||
* @param userId
|
||||
* @param audioFileId
|
||||
* @returns sorted tasks
|
||||
*/
|
||||
async getSortedTasks(
|
||||
accountId: number,
|
||||
userId: number,
|
||||
audioFileId: number,
|
||||
): Promise<Task[]> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const taskRepo = entityManager.getRepository(Task);
|
||||
const sortRepo = entityManager.getRepository(SortCriteria);
|
||||
|
||||
const sort = await sortRepo.findOne({ where: { user_id: userId } });
|
||||
|
||||
// 運用上はあり得ないが、プログラム上発生しうるのでエラーとして処理
|
||||
if (!sort) {
|
||||
throw new Error(`sort criteria not found. userId: ${userId}`);
|
||||
}
|
||||
|
||||
const { direction, parameter } = sort;
|
||||
//型チェック
|
||||
if (
|
||||
!isTaskListSortableAttribute(parameter) ||
|
||||
!isSortDirection(direction)
|
||||
) {
|
||||
throw new Error(
|
||||
`The value stored in the DB is invalid. parameter: ${parameter}, direction: ${direction}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 指定した音声ファイルIDのタスクを取得
|
||||
const targetTask = await taskRepo.findOne({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
audio_file_id: audioFileId,
|
||||
status: In([
|
||||
TASK_STATUS.PENDING,
|
||||
TASK_STATUS.FINISHED,
|
||||
TASK_STATUS.UPLOADED,
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
if (!targetTask) {
|
||||
throw new TasksNotFoundError(
|
||||
`target task not found. audioFileId: ${audioFileId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const groupMemberRepo = entityManager.getRepository(UserGroupMember);
|
||||
// ユーザーの所属するすべてのグループを列挙
|
||||
const groups = await groupMemberRepo.find({ where: { user_id: userId } });
|
||||
// ユーザーの所属するすべてのグループIDを列挙
|
||||
const groupIds = groups.map((member) => member.user_group_id);
|
||||
|
||||
const checkoutRepo = entityManager.getRepository(CheckoutPermission);
|
||||
// ユーザーに対するチェックアウト権限、またはユーザーの所属するユーザーグループのチェックアウト権限を取得
|
||||
const related = await checkoutRepo.find({
|
||||
where: [
|
||||
// ユーザーがチェックアウト可能である
|
||||
{ user_id: userId },
|
||||
// ユーザーの所属するユーザーグループがチェックアウト可能である
|
||||
{ user_group_id: In(groupIds) },
|
||||
],
|
||||
});
|
||||
|
||||
// ユーザー本人、またはユーザーが所属するユーザーグループがチェックアウト可能なタスクIDの一覧を作成
|
||||
const relatedTaskIds = related.map((permission) => permission.task_id);
|
||||
|
||||
const order = makeOrder(parameter, direction);
|
||||
|
||||
// 引数の音声ファイルIDで指定したタスクとユーザが着手可能なタスクの一覧を取得
|
||||
const tasks = await taskRepo.find({
|
||||
where: [
|
||||
{
|
||||
account_id: accountId,
|
||||
id: targetTask.id,
|
||||
},
|
||||
{
|
||||
account_id: accountId,
|
||||
status: In([TASK_STATUS.UPLOADED, TASK_STATUS.PENDING]),
|
||||
// TypistまたはTypistが所属するユーザーグループが割り当て可能になっているTaskを取得
|
||||
id: In(relatedTaskIds),
|
||||
},
|
||||
],
|
||||
order: order,
|
||||
});
|
||||
|
||||
return tasks;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ソート用オブジェクトを生成する
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user